13

Suppose there is a Page class, which represents a set of instructions to a page renderer. And Suppose there is a Renderer class that knows how to render a page on screen. It is possible to structure code in two different ways:

/*
 * 1) Page Uses Renderer internally,
 * or receives it explicitly
 */
$page->renderMe(); 
$page->renderMe($renderer); 

/*
 * 2) Page is passed to Renderer
 */
$renderer->renderPage($page);

What are the pros and cons of each approach? When will one be better? When will the other be better?


BACKGROUND

To add a little bit more background - I am finding myself using both approaches in the same code. I am using a 3rd party PDF library called TCPDF. Somewhere in my code I have to have the following for PDF rendering to work:

$pdf = new TCPDF();
$html = "some text";
$pdf->writeHTML($html);

Say I wish to create a representation of the page. I could create a template that holds instructions to render a PDF page snippet like so:

/*
 * A representation of the PDF page snippet:
 * a template directing how to render a specific PDF page snippet
 */
class PageSnippet
{    
    function runTemplate(TCPDF $pdf, array $data = null): void
    {
        $pdf->writeHTML($data['html']);
    }
}

/* To be used like so */
$pdf = new TCPDF();
$data['html'] = "some text";
$snippet = new PageSnippet();
$snippet->runTemplate($pdf, $data);

1) Notice here that $snippet runs itself, as in my first code example. It also needs to know and be familiar with the $pdf, and with any $data for it to work.

But, I can create a PdfRenderer class like so:

class PdfRenderer
{
    /**@var TCPDF */
    protected $pdf;

    function __construct(TCPDF $pdf)
    {
        $this->pdf = $pdf;
    }

    function runTemplate(PageSnippet $template, array $data = null): void
    {
        $template->runTemplate($this->pdf, $data);
    }
}

and then my code turns to this:

$renderer = new PdfRenderer(new TCPDF());
$renderer->runTemplate(new PageSnippet(), array('html' => 'some text'));

2) Here the $renderer receives the PageSnippet and any $data required for it to work. This is similar to my second code example.

So, even though the renderer receives the page snippet, inside the renderer, the snippet still runs itself. That is to say that both approaches are in play. I am not sure if you can restrict your OO usage to only one or only the other. Both might be required, even if you mask one by the other.

8
  • 2
    Unfortunately, you have wandered into the world of software "religious wars" here, along the lines of whether to use spaces or tabs, which brace style to use, etc. There is no "better" here, just strong opinions on both sides. Do an internet search of the benefits and disadvantages of both the rich and anaemic domain models and form your own opinion.
    – David Arno
    Commented Feb 5, 2018 at 21:43
  • 7
    @DavidArno Use spaces you heathen! :) Commented Feb 5, 2018 at 21:45
  • 1
    Ha, I seriously don't understand this site at times. Perfectly good questions that get good answers are closed in no time as being opinion-based. Yet an obviously opinion-based question like this comes along and those usual suspects are nowhere to be found. Oh well, if you can't beat them and all that... :)
    – David Arno
    Commented Feb 6, 2018 at 8:53
  • @Erik Eidt, could you undelete your answer please as I feel it as very good "forth option" answer.
    – David Arno
    Commented Feb 6, 2018 at 11:56
  • 1
    Aside from SOLID principles, you may take a look at GRASP, especially on Expert part. The question is which has the information for you to fulfill the responsibility? Commented Feb 7, 2018 at 2:50

7 Answers 7

13

This depends entirely on what you think OO is.

For OOP = SOLID, the operation should be part of the class if it is part of the Single Responsibility of the class.

For OO = virtual dispatch/polymorphism, the operation should be part of the object if it should be dispatched dynamically, i.e. if it is called through an interface.

For OO = encapsulation, the operation should be part of the class if it uses internal state that you don't want to expose.

For OO = “I like fluent interfaces”, the question is which variant reads more naturally.

For OO = modelling real-world entities, which real-world entity performs this operation?


All of those viewpoints are usually wrong in isolation. But sometimes one or more of these perspectives are helpful at arriving at a design decision.

E.g. using the polymorphism viewpoint: If you have different rendering strategies (like different output formats, or different rendering engines), then $renderer->render($page) makes a lot of sense. But if you have different page types that should be rendered differently, $page->render() might be better. If the output depends on both the page type and the rendering strategy, you can do double dispatch through the visitor pattern.

Don't forget that in many languages, functions don't have to be methods. A simple function like render($page) if often a perfectly fine (and wonderfully simple) solution.

6
  • Er wait a minute. I can still get polymorphic rendering if the page holds a reference to the renderer but has no idea which renderer it holds. It just means the polymorphism is a little further down the rabbit hole. I also can pick and choose what to pass to the renderer. I don't have to pass the whole page. Commented Feb 5, 2018 at 21:57
  • @CandiedOrange That's a good point, but I would book your argument under the SRP: it would be the Page's capital-R Responsibility to decide how it is rendered, maybe using some kind of polymorphic rendering strategy.
    – amon
    Commented Feb 5, 2018 at 22:00
  • I figured the $renderer was going to decide how to render. When the $page talks to the $renderer all it says is what to render. Not how. The $page has no idea how. That gets me into SRP trouble? Commented Feb 5, 2018 at 22:08
  • I really don't think we are disagreeing. I was trying to sort your first comment into the conceptual framework of this answer, but I may have used clumsy words. One thing you're reminding me of that I didn't mention in the answer: tell-don't-ask data flow is also a good heuristic.
    – amon
    Commented Feb 5, 2018 at 22:12
  • Hmm ok. You're right. What I've been talking about would follow tell-don't-ask. Now correct me if I'm wrong. The other strategy, where the renderer takes a page reference, means the renderer would have to turn around and ask the page for stuff, using the pages getters. Commented Feb 5, 2018 at 22:17
3

According to Alan Kay, objects are self-sufficient, "adult" and responsible organisms. Adults do things, they are not operated upon. That is, financial transaction is responsible for saving itself, page is responsible for rendering itself, etc, etc. More concisely, encapsulation is the big thing in OOP. In particular, it manifests through the famous Tell don't ask principle (that @CandiedOrange likes to mention all the time :) ) and public reprobation of getters and setters.

In practice it results in objects possessing all necessary resources to do their job, like database facilities, rendering facilities, etc.

So considering your example, my OOP-version would look like the following:

class Page
{
    private $data;
    private $renderer;

    public function __construct(ICanRender $renderer, $data)
    {
        $this->renderer = $renderer;
        $this->data = $data;
    }

    public function render()
    {
        $this->renderer->render($this->data);
    }
}

In case you're interested, David West talks about the original OOP principles in his book, Object Thinking.

16
  • 1
    To put it bluntly, who cares what someone said about something to do with software development, 15 years ago, except due to historical interest?
    – David Arno
    Commented Feb 6, 2018 at 11:26
  • 1
    "I do care what a man who invented the Object-oriented concept said about what object is." Why? Beyond lulling you into using "appeal to authority" fallacies in your arguments, what possible bearing could the thoughts of the inventor of a term have upon the term's application 15 years later?
    – David Arno
    Commented Feb 6, 2018 at 12:18
  • 2
    @Zapadlo: You do not present an argument why the message is from Page to Renderer and not the other way around. They are both object, and hence both adults, right?
    – JacquesB
    Commented Feb 6, 2018 at 12:53
  • 1
    "Appeal to authority fallacy can not be applied here" ... "So the set of concepts that in your opinion represents OOP, is actually wrong [because it's a distortion of the original definition]". I take it you do not know what an appeal to authority fallacy is? Clue: you used one here. :)
    – David Arno
    Commented Feb 6, 2018 at 13:22
  • 1
    @David Arno So, are all appeals to authority wrong? Would you prefer "Appeal to my opinion?' Everytime somebody cites an Uncle Bobism, will you complain about appeal to authority? Zapadio provided a well respected source. You can disagree, or cite conflicting sources, but repeatefly complaining that somebody had provided a citation is not constructive.
    – user949300
    Commented Feb 7, 2018 at 3:09
2

$page->renderMe();

Here we have page being completely responsible for rendering itself. It may have been supplied with a render via a constructor, or it may have that functionality built in.

I'll ignore the first case (supplied with a render via a constructor) here, as it's pretty similar to passing it as a parameter. Instead I'll look at the pros and cons of the functionality being built in.

The pro is that it allows a very high level of encapsulation. The page need reveal nothing about its inner state directly. It only exposes it via a rendering of itself.

The con is that it breaks the single responsibility principle (SRP). We have a class that's responsible for encapsulating a page's state and is also hard-coded with rules on how to render itself and thus likely a whole range of other responsibilities as objects should "do things to themselves, not have things done to them by others".

$page->renderMe($renderer);

Here, we are still requiring a page to be able to render itself, but we are supplying it with a helper object that can do the actual rendering. Two scenarios can arise here:

  1. The page simply needs to know the rendering rules (which methods to call in which order) in order to create that render. Encapsulation is preserved, but the SRP is still broken as the page still has to oversee the rendering process, or
  2. The page just calls one method on the renderer object, passing its details in. We are getting closer to respecting the SRP, but we have now weakened encapsulation.

$renderer->renderPage($page);

Here, we have fully respected the SRP. The page object is responsible for holding information on a page and the renderer is responsible for rendering that page. However, we have now completely weakened the encapsulation of the page object as it needs to make its whole state, public.

Also, we have created a new problem: the renderer is now tightly coupled to the page class. What happens when we want to render something different to a page?

Which is best? None of them. They all have their flaws.

1
  • Disagree that V3 respects SRP. Renderer has at least 2 reasons to change: if Page changes, or if the way you render it changes. And a third, which you do cover, if Renderer needs to render objects other than Pages. Otherwise, nice analysis.
    – user949300
    Commented Feb 7, 2018 at 8:21
2

The answer to this question is unequivocal. It is $renderer->renderPage($page); which is the correct implementation. To understand how we arrived at this conclusion, we need to understand encapsulation.

What is a page? It is a representation of a display which someone will consume. That "someone" could be human or bots. Note that the Page is a representation, and not the display itself. Does a representation exist without being represented? Is a page something without renderer? Answer is Yes, a representation can exist without being represented. To represent is a later stage.

What is a renderer without a page? Can a renderer render without a page? No. So a Renderer interface does need the renderPage($page); method.

What's wrong with $page->renderMe($renderer);?

It is the fact that renderMe($renderer) will still have to internally call $renderer->renderPage($page);. This violates Law of Demeter which states

Each unit should have only limited knowledge about other units

The Page class does not care whether there exists a Renderer in the universe. It only cares about being a representation of a page. So the class or interface Renderer should never be mentioned inside a Page.


UPDATED ANSWER

If I got your question correct, the PageSnippet class should only be concerned with being a page snippet.

class PageSnippet
{    
    /** string */
    private $html;

    function __construct($data = ['html' => '']): void
    {
        $this->html = $data['html'];
    }

   public function getHtml()
   {
       return $this->html;
   }
}

PdfRenderer is concerned with rendering.

class PdfRenderer
{
    /**@var TCPDF */
    protected $pdf;

    function __construct(TCPDF $pdf = new TCPDF())
    {
        $this->pdf = $pdf;
    }

    function runTemplate(string $html): void
    {
        $this->pdf->writeHTML($html);
    }
}

Client usage

$renderer = new PdfRenderer();
$snippet = new PageSnippet(['html' => '<html />']);
$renderer->runTemplate($snippet->getHtml());

Couple of points to consider:

  • Its bad practice to pass around $data as an associative array. It should be an instance of a class.
  • The fact that page format is contained inside html property of the $data array is a details specific to your domain, and PageSnippet is aware of this details.
6
  • But, what if, in addition to Pages, you have Pictures, Articles, and Triptichs? In your scheme, a Renderer would have to know about all of them. That's a lot of of leakage. Just food for thought.
    – user949300
    Commented Feb 6, 2018 at 8:24
  • @user949300: Well if the Renderer needs to be able render pictures etc. then obviously it needs to know about them.
    – JacquesB
    Commented Feb 6, 2018 at 10:04
  • 2
    Smalltalk Best Practice Patterns by Kent Beck introduces the Reversing Method pattern, in which both of these are supported. The linked article shows that an object supports a printOn:aStream method, but all that it does is tell the stream to print the object. The analogy with your answer is that there's no reason you couldn't have both a page that can be rendered to a renderer and a renderer that can render a page, with one implementation and a choice of convenient interfaces.
    – Graham Lee
    Commented Feb 6, 2018 at 14:25
  • 2
    You are going to have to break/fudge SRP in any case, but if Renderer needs to know how to render many many different things, that really is "many many responsibility", and, if possible, to be avoided.
    – user949300
    Commented Feb 7, 2018 at 8:15
  • 1
    I like your answer but I am tempted to think that Page not being aware of $renderer is impossible. I added some code into my question, see PageSnippet class. It is effectively a page, but it cannot exist without making some kind of reference to the $pdf, which is in fact a 3rd party PDF renderer in this case. .. However, I suppose though I could create such a PageSnippet class that only holds an array of text instructions to the PDF, and have some other class interpret those instructions. That way I can avoid injecting $pdf into PageSnippet, at the expense of extra complexity
    – Dennis
    Commented Feb 7, 2018 at 19:14
1

Ideally, you want as few dependencies between classes as possible, since it reduces complexity. A class should only have a dependency to another class if it really needs it.

You state Page contains "a set of instructions to a page renderer". I imagine something like this:

renderer.renderLine(x, y, w, h, Color.Black)
renderer.renderText(a, b, Font.Helvetica, Color.Black, "bla bla...")
etc...

So it would be $page->renderMe($renderer), since the Page needs a reference to renderer.

But alternatively rendering instructions could also be expressed as a data structure rather than direct calls, eg.

[
  Line(x, y, w, h, Color.Black), 
  Text(a, b, Font.Helvetica, Color.Black, "bla bla...")
]

In this case the actual Renderer would get this data structure from the Page and process it by execute the corresponding rendering instructions. With such an approach the dependencies would be reversed - the Page does not need to know about the Renderer, but the Renderer should be provided a Page which it can then render. So option two: $renderer->renderPage($page);

So which is best? The first approach is probably simplest to implement, while the second is much more flexible and powerful, so I guess it depends on your requirements.

If you cannot decide, or you think you might change approach in the future, you can hide the decision behind a layer of indirection, a function:

renderPage($page, $renderer)

The only approach I will not recommend is $page->renderMe() since it suggest a page can have only a single renderer. But what if you have a ScreenRenderer and add a PrintRenderer? The same page might be rendered by both.

3
  • In the context of EPUB or HTML, the concept of page doesn't exist without a renderer.
    – mouviciel
    Commented Feb 6, 2018 at 10:27
  • 1
    @mouviciel: I'm not sure I understand what you mean. Surely you can have a HTML page without rendering it? E.g. the Google crawler process pages without rendering them.
    – JacquesB
    Commented Feb 6, 2018 at 11:23
  • 2
    There is a different notion of the word page: the result of a pagination process when an HTML page formatted to be printed, maybe that what is what @mouviciel had in mind. However, in this question a page is clearly an input to the renderer, not an output, to that notion clearly does not fit.
    – Doc Brown
    Commented Feb 6, 2018 at 12:14
1

The D part of SOLID says

"Abstractions should not depend on details. Details should depend on abstractions."

So, between Page and Renderer, which is more likely to be a stable abstraction, less likely to change, possibly representing an interface? Contrariwise, which is the "detail"?

In my experience, the abstraction is usually the Renderer. For example, it might a simple Stream or XML, very abstract and stable. Or some fairly standard layout. Your Page is more likely to be a custom business object, a "detail". And you have other business objects to be rendered, such as "pictures", "reports", "charts" etc... (Probably not a "tryptich" as in my comment)

But it obviously depends on your design. Page could be abstract, for example the equivalent of an HTML <article> tag with standard subparts. And you have lots of different custom business reporting "renderers". In that case, the Renderer should depend on the Page.

0

I think most Classes can be split in either one of two categories:

  • Classes containing data (mutable or immutable doesn't matter)

These are classes that have almost no dependencies on anything else. They're typically part of your domain. They should contain no logic or only logic that can be derived directly from its state. An Employee class can have a function isAdult as that can be derived directly from its birthDate but not a function hasBirthDay as that requires external information (the current date).

  • Classes that provide services

These types of classes operate on other classes containing data. They are typically configured once and immutable (so they always perform the same kind of function). These kinds of classes may however still provide a stateful short-lived helper instance to do more complex operations that require maintaining some state for a short period (like Builder classes).

Your example

In your example, Page would be a class containing data. It should have functions to get this data and perhaps modify it if the class is supposed to be mutable. Keep it dumb, so it can be used without a lot of dependencies.

Data, or in this case your Page could be represented in a multitude of ways. It could be rendered as a webpage, written to disk, stored in a database, converted to JSON, whatever. You donot want to add methods to such a class for each of these cases (and create dependencies on all kinds of other classes, even though your class is supposed to just contain data).

Your Renderer is a typical service type class. It can operate on a certain set of data and return a result. It doesn't have much state of its own, and what state it has is usually immutable, can be configured once and then re-used.

For example, you could have a MobileRenderer and a StandardRenderer, both implementations of the Renderer class but with different settings.

So as Page contains data and should be kept dumb, the cleanest solution in this case would be to pass the Page to a Renderer:

$renderer->renderPage($page)
1
  • 2
    Very procedural logic.
    – user949300
    Commented Feb 9, 2018 at 22:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.