Wann sollten Objekte in objektorientierten Sprachen Operationen an sich selbst ausführen und wann sollten Operationen an Objekten ausgeführt werden?

11

Angenommen, es gibt eine PageKlasse, die eine Reihe von Anweisungen für einen Seitenrenderer darstellt. Angenommen, es gibt eine RendererKlasse, die weiß, wie eine Seite auf dem Bildschirm gerendert wird. Code kann auf zwei verschiedene Arten strukturiert werden:

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

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

Welche Vor- und Nachteile hat jeder Ansatz? Wann wird man besser sein? Wann wird der andere besser sein?


HINTERGRUND

Um ein bisschen mehr Hintergrund hinzuzufügen - ich finde mich dabei, beide Ansätze im selben Code zu verwenden. Ich verwende eine PDF-Bibliothek eines Drittanbieters namens TCPDF. Irgendwo in meinem Code muss ich Folgendes haben , damit das PDF-Rendering funktioniert:

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

Angenommen, ich möchte eine Darstellung der Seite erstellen. Ich könnte eine Vorlage erstellen, die Anweisungen zum Rendern eines PDF-Seitenausschnitts enthält:

/*
 * 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) Beachten Sie hier, dass es $snippet sich wie in meinem ersten Codebeispiel selbst ausführt. Es muss auch wissen und vertraut sein mit $pdfund mit jedem, $datadamit es funktioniert.

Aber ich kann eine PdfRendererKlasse wie folgt erstellen :

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);
    }
}

und dann dreht sich mein Code dazu:

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

2) Hier $renderererhält der PageSnippetund alle dafür $datanotwendigen Arbeiten. Dies ähnelt meinem zweiten Codebeispiel.

Obwohl der Renderer das Seiten-Snippet empfängt, wird das Snippet im Renderer dennoch selbst ausgeführt . Das heißt, beide Ansätze spielen eine Rolle. Ich bin mir nicht sicher, ob Sie Ihre OO-Nutzung auf den einen oder den anderen beschränken können. Beides kann erforderlich sein, auch wenn Sie eines nach dem anderen maskieren.

Dennis
quelle
2
Leider sind Sie hier in die Welt der Software "Religionskriege" geraten, in der es darum geht, Leerzeichen oder Tabulatoren zu verwenden, die den Stil einschränken usw. Hier gibt es kein "Besseres", nur starke Meinungen auf beiden Seiten. Machen Sie eine Internetsuche nach den Vor- und Nachteilen der Modelle für reiche und anämische Domänen und bilden Sie sich Ihre eigene Meinung.
David Arno
7
@DavidArno Benutze Räume, die du heidest ! :)
candied_orange
1
Ha, ich verstehe diese Seite manchmal ernsthaft nicht. Perfekt gute Fragen, die gute Antworten bekommen, sind in kürzester Zeit als meinungsbasiert abgeschlossen. Eine offensichtlich meinungsbasierte Frage wie diese taucht jedoch auf und die üblichen Verdächtigen sind nirgends zu finden. Na ja, wenn du sie nicht schlagen kannst und das alles ... :)
David Arno
@Erik Eidt, könntest du bitte deine Antwort rückgängig machen, da ich es als sehr gute "Vierte Option" Antwort empfinde.
David Arno
1
Abgesehen von den SOLID-Grundsätzen können Sie sich GRASP ansehen , insbesondere den Expertenteil . Die Frage ist, welche Informationen Sie haben, um die Verantwortung zu erfüllen?
OnesimusUnbound

Antworten:

13

Dies hängt ganz davon ab, was Sie für OO halten .

Bei OOP = SOLID sollte die Operation Teil der Klasse sein, wenn sie Teil der Einzelverantwortung der Klasse ist.

Bei OO = virtueller Versand / Polymorphismus sollte die Operation Teil des Objekts sein, wenn sie dynamisch versendet werden soll, dh wenn sie über eine Schnittstelle aufgerufen wird.

Für die OO = -Kapselung sollte die Operation Teil der Klasse sein, wenn sie einen internen Status verwendet, den Sie nicht verfügbar machen möchten.

Für OO = „Ich mag fließende Schnittstellen“ lautet die Frage, welche Variante natürlicher liest.

Für OO = Modellieren von Entitäten in der realen Welt, welche Entität in der realen Welt führt diese Operation aus?


Alle diese Standpunkte sind normalerweise für sich genommen falsch. Manchmal sind jedoch eine oder mehrere dieser Perspektiven hilfreich, um eine Entwurfsentscheidung zu treffen.

ZB unter dem Gesichtspunkt des Polymorphismus: Wenn Sie unterschiedliche Rendering-Strategien (wie unterschiedliche Ausgabeformate oder unterschiedliche Rendering-Engines) haben, $renderer->render($page)ist dies sehr sinnvoll. Wenn Sie jedoch unterschiedliche Seitentypen haben, die unterschiedlich gerendert werden sollen,$page->render() möglicherweise besser. Wenn die Ausgabe sowohl vom Seitentyp als auch von der Rendering-Strategie abhängt, können Sie über das Besuchermuster einen doppelten Versand durchführen.

Vergessen Sie nicht, dass Funktionen in vielen Sprachen keine Methoden sein müssen. Eine einfache Funktion, wie render($page)wenn auch oft eine vollkommen feine (und wunderbar einfache) Lösung.

amon
quelle
Er warte eine Minute. Ich kann immer noch ein polymorphes Rendering erhalten, wenn die Seite einen Verweis auf den Renderer enthält, aber keine Ahnung hat, welchen Renderer er enthält. Es bedeutet nur, dass der Polymorphismus etwas weiter unten im Kaninchenbau liegt. Ich kann auch auswählen, was an den Renderer übergeben werden soll. Ich muss nicht die ganze Seite durchgehen.
candied_orange
@CandiedOrange Das ist ein guter Punkt, aber ich würde Ihr Argument unter dem SRP verbuchen: Es wäre die Verantwortung der Seite, zu entscheiden, wie es gerendert wird, möglicherweise unter Verwendung einer polymorphen Rendering-Strategie.
Am
Ich dachte, das $rendererwürde entscheiden, wie gerendert werden soll. Bei den $pageGesprächen mit dem $rendererGanzen heißt es, was zu rendern ist. Nicht wie. Der $pagehat keine Ahnung wie. Das bringt mich in SRP-Probleme?
candied_orange
Ich glaube wirklich nicht, dass wir nicht einverstanden sind. Ich habe versucht, Ihren ersten Kommentar in den konzeptuellen Rahmen dieser Antwort zu sortieren, aber ich habe möglicherweise unbeholfene Wörter verwendet. Sie erinnern mich daran, dass ich in der Antwort nicht erwähnt habe: Der Datenfluss "Tell-Don't-Ask" ist auch eine gute Heuristik.
Amon
Hmm ok Du hast recht. Wovon ich gesprochen habe, würde "Tell-Don't-Ask" folgen. Korrigiere mich jetzt, wenn ich falsch liege. Die andere Strategie, bei der der Renderer eine Seitenreferenz verwendet, bedeutet, dass der Renderer sich umdrehen und die Seite mithilfe der Seitenleser nach Dingen fragen muss.
candied_orange
2

Laut Alan Kay sind Objekte autark, "erwachsen" und verantwortliche Organismen. Erwachsene machen Dinge, sie werden nicht operiert. Das heißt, die Finanztransaktion ist für das Speichern selbst verantwortlich , die Seite ist für das Rendern selbst usw. usw. verantwortlich. Genauer gesagt, die Kapselung ist die große Sache in OOP. Insbesondere manifestiert es sich durch das berühmte Tell-Don't-Ask-Prinzip (das @CandiedOrange immer wieder erwähnt :)) und die öffentliche Ablehnung von Gettern und Setzern .

In der Praxis führt dies dazu, dass Objekte über alle für ihre Arbeit erforderlichen Ressourcen verfügen, z. B. Datenbankfunktionen, Renderfunktionen usw.

In Anbetracht Ihres Beispiels würde meine OOP-Version folgendermaßen aussehen:

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);
    }
}

Falls Sie interessiert sind, spricht David West in seinem Buch Object Thinking über die ursprünglichen OOP-Prinzipien .

Zapadlo
quelle
1
Um es ganz klar auszudrücken: Wen interessiert es, was jemand über etwas gesagt hat, das mit der Softwareentwicklung vor 15 Jahren zu tun hat, außer aus historischen Gründen?
David Arno
1
" Es ist mir egal, was ein Mann, der das objektorientierte Konzept erfunden hat, über das Objekt gesagt hat. " Warum? Was könnte der Erfinder eines Begriffs davon halten, 15 Jahre später den Begriff anzuwenden?
David Arno
2
@Zapadlo: Sie führen kein Argument an, warum die Nachricht von Seite zu Renderer und nicht umgekehrt gesendet wird. Sie sind beide Objekte und daher beide Erwachsene, richtig?
JacquesB
1
"Ein Appell an den Autoritätsmissbrauch kann hier nicht angewendet werden " ... " Also ist die Reihe von Konzepten, die Ihrer Meinung nach OOP darstellt, tatsächlich falsch [weil sie eine Verzerrung der ursprünglichen Definition darstellt] ". Ich nehme an, Sie wissen nicht, was ein Appell an Autoritätsfehler ist? Hinweis: Sie haben hier eine verwendet. :)
David Arno
1
@ David Arno Also, sind alle Appelle an die Behörde falsch? Möchten Sie lieber "Meiner Meinung nach ansprechen?" Jedesmal , jemand nennt einen Onkel Bobism, werden Sie über Appell an Autorität beschweren Zapadio bot eine angesehene Quelle kann Sie nicht einverstanden ist , oder zu zitieren Quellen in Konflikt, aber repeatefly beschweren , dass jemand ein Zitat zur Verfügung gestellt hatte nicht konstruktiv ist?..
user949300
2

$page->renderMe();

Hier haben wir page vollständig für das Rendern selbst verantwortlich. Sie wurde möglicherweise über einen Konstruktor mit einem Rendering ausgeliefert oder verfügt möglicherweise über diese integrierte Funktionalität.

Ich werde den ersten Fall (der mit einem Render über einen Konstruktor geliefert wird) hier ignorieren, da er der Übergabe als Parameter ziemlich ähnlich ist. Stattdessen werde ich die Vor- und Nachteile der eingebauten Funktionalität betrachten.

Der Vorteil ist, dass es ein sehr hohes Maß an Einkapselung ermöglicht. Die Seite muss direkt nichts über ihren inneren Zustand preisgeben. Es macht es nur über ein Rendering von sich selbst verfügbar.

Der Nachteil ist, dass das Prinzip der einmaligen Verantwortung (Single Responsibility Principle, SRP) verletzt wird. Wir haben eine Klasse, die für die Verkapselung des Zustands einer Seite verantwortlich ist und die auch Regeln für die Darstellung selbst und damit wahrscheinlich eine ganze Reihe anderer Verantwortlichkeiten enthält, da Objekte "sich selbst etwas antun sollten, ohne dass andere Dinge damit anstellen ".

$page->renderMe($renderer);

Hier benötigen wir noch eine Seite, um sich selbst rendern zu können, aber wir liefern ihr ein Hilfsobjekt, das das eigentliche Rendern übernehmen kann. Hierbei können zwei Szenarien auftreten:

  1. Die Seite muss lediglich die Rendering-Regeln kennen (welche Methoden in welcher Reihenfolge aufgerufen werden sollen), um das Rendering zu erstellen. Die Kapselung bleibt erhalten, die SRP ist jedoch weiterhin fehlerhaft, da die Seite den Renderprozess noch überwachen muss, oder
  2. Die Seite ruft nur eine Methode für das Renderer-Objekt auf und übergibt deren Details. Wir nähern uns dem Respektieren des SRP, haben aber jetzt die Kapselung geschwächt.

$renderer->renderPage($page);

Hier haben wir die SRP voll respektiert. Das Seitenobjekt ist für das Speichern von Informationen auf einer Seite verantwortlich, und der Renderer ist für das Rendern dieser Seite verantwortlich. Wir haben jedoch die Kapselung des Seitenobjekts jetzt vollständig abgeschwächt, da es seinen gesamten Zustand öffentlich machen muss.

Außerdem haben wir ein neues Problem erstellt: Der Renderer ist jetzt eng an die Seitenklasse gekoppelt. Was passiert, wenn wir etwas anderes als eine Seite rendern möchten?

Welches das Beste ist? Keines von denen. Sie haben alle ihre Fehler.

David Arno
quelle
Stimme nicht zu, dass V3 SRP respektiert. Der Renderer hat mindestens zwei Gründe, sich zu ändern: Wenn sich die Seite ändert oder wenn sich die Art und Weise ändert, wie Sie sie rendern. Und ein drittes, das Sie behandeln, wenn Renderer andere Objekte als Pages rendern muss. Ansonsten schöne Analyse.
user949300
2

Die Antwort auf diese Frage ist eindeutig. Es ist$renderer->renderPage($page); das, was die richtige Implementierung ist. Um zu verstehen, wie wir zu dieser Schlussfolgerung gekommen sind, müssen wir die Verkapselung verstehen.

Was ist eine Seite? Es ist eine Darstellung eines Displays, das jemand konsumieren wird. Dieser "Jemand" könnte ein Mensch oder ein Roboter sein. Beachten Sie, dass dies Pageeine Darstellung ist und nicht die Anzeige selbst. Existiert eine Repräsentation ohne repräsentiert zu sein? Ist eine Seite etwas ohne Renderer? Die Antwort lautet Ja, eine Repräsentation kann existieren, ohne repräsentiert zu sein. Darstellen ist eine spätere Phase.

Was ist ein Renderer ohne Seite? Kann ein Renderer ohne Seite rendern? Nein. Eine Renderer-Oberfläche benötigt diese renderPage($page);Methode.

Was ist los mit $page->renderMe($renderer);?

Es ist die Tatsache, dass renderMe($renderer)noch intern anrufen müssen $renderer->renderPage($page);. Dies verstößt gegen das Gesetz von Demeter, das besagt

Jede Einheit sollte nur begrenzte Kenntnisse über andere Einheiten haben

Der PageKlasse ist es egal, ob es Rendererim Universum eine gibt. Es geht nur darum, eine Seite darzustellen. Daher sollte die Klasse oder Schnittstelle Rendererniemals in einem angegeben werden Page.


AKTUALISIERTE ANTWORT

Wenn ich Ihre Frage richtig gestellt habe, sollte sich die PageSnippetKlasse nur darum kümmern, ein Seitenausschnitt zu sein.

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

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

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

PdfRenderer befasst sich mit 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-Nutzung

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

Einige Punkte zu beachten:

  • Es ist eine schlechte Praxis, herumzugeben $data als assoziatives Array auszugeben. Es sollte eine Instanz einer Klasse sein.
  • Die Tatsache, dass das Seitenformat in der htmlEigenschaft des $dataArrays enthalten ist, ist spezifisch für Ihre Domain und PageSnippetkennt diese Details.
Juzer Ali
quelle
Aber was ist, wenn Sie neben Pages auch Bilder, Artikel und Triptiche haben? In Ihrem Schema müsste ein Renderer alle kennen. Das ist eine Menge Leckage. Nur zum Nachdenken anregen.
user949300
@ user949300: Nun, wenn der Renderer in der Lage sein muss, Bilder usw. zu rendern, muss er natürlich etwas darüber wissen.
JacquesB
1
Smalltalk Best Practice Patterns von Kent Beck führt das Reversing Method Pattern ein, in dem beide unterstützt werden. Der verknüpfte Artikel zeigt, dass ein Objekt eine printOn:aStreamMethode unterstützt , der Stream jedoch nur angewiesen wird, das Objekt zu drucken. Die Analogie zu Ihrer Antwort ist, dass es keinen Grund gibt, nicht sowohl eine Seite, die in einem Renderer gerendert werden kann, als auch einen Renderer, der eine Seite rendern kann, mit einer Implementierung und einer Auswahl praktischer Schnittstellen.
Graham Lee
2
Sie werden in jedem Fall SRP brechen / fudgen müssen, aber wenn Renderer wissen muss, wie man viele verschiedene Dinge rendert, dann ist das wirklich "viele, viele Verantwortung" und sollte nach Möglichkeit vermieden werden.
user949300
1
Ich mag Ihre Antwort, aber ich bin versucht zu glauben, dass Pagees unmöglich ist, $ renderer nicht zu kennen. Ich habe meiner Frage Code hinzugefügt, siehe PageSnippetKlasse. $pdfEigentlich ist es eine Seite, aber es kann nicht existieren, ohne auf die zu verweisen, die in diesem Fall ein PDF-Renderer von Drittanbietern ist. Ich nehme jedoch an, dass ich eine solche PageSnippetKlasse erstellen könnte, die nur eine Reihe von Textanweisungen für das PDF enthält, und dass eine andere Klasse diese Anweisungen interpretiert. So kann ich vermeiden , kann die Injektion $pdfin PageSnippet, auf Kosten der zusätzlichen Komplexität
Dennis
1

Idealerweise möchten Sie so wenig Abhängigkeiten wie möglich zwischen Klassen, da dies die Komplexität verringert. Eine Klasse sollte nur dann von einer anderen Klasse abhängig sein, wenn sie diese wirklich benötigt.

Sie geben an, Page"eine Reihe von Anweisungen für einen Seitenrenderer" zu enthalten. Ich stelle mir so etwas vor:

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

So wäre es $page->renderMe($renderer), da die Seite benötigt einen Verweis auf Renderer.

Alternativ könnte das Rendern von Anweisungen auch als Datenstruktur anstatt als direkte Aufrufe ausgedrückt werden, z.

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

In diesem Fall würde der eigentliche Renderer diese Datenstruktur von der Seite abrufen und sie verarbeiten, indem er die entsprechenden Renderanweisungen ausführt. Bei einem solchen Ansatz würden die Abhängigkeiten umgekehrt - die Seite muss nichts über den Renderer wissen, aber dem Renderer sollte eine Seite bereitgestellt werden, die er dann rendern kann. Also Option zwei:$renderer->renderPage($page);

Welches ist das Beste? Der erste Ansatz ist wahrscheinlich am einfachsten zu implementieren, während der zweite Ansatz viel flexibler und leistungsfähiger ist. Ich denke, das hängt von Ihren Anforderungen ab.

Wenn Sie sich nicht entscheiden können oder glauben, Sie könnten in Zukunft den Ansatz ändern, können Sie die Entscheidung hinter einer Indirektionsebene, einer Funktion, verbergen:

renderPage($page, $renderer)

Der einzige Ansatz, den ich nicht empfehlen werde, ist der $page->renderMe(), dass eine Seite nur einen einzigen Renderer haben kann. Aber was ist, wenn Sie eine haben ScreenRendererund eine hinzufügen PrintRenderer? Dieselbe Seite wird möglicherweise von beiden gerendert.

JacquesB
quelle
Im Kontext von EPUB oder HTML existiert das Konzept der Seite nicht ohne einen Renderer.
Mouviciel
1
@mouviciel: Ich bin nicht sicher, ob ich verstehe, was du meinst. Sicherlich können Sie eine HTML-Seite haben, ohne sie zu rendern? Beispielsweise verarbeitet der Google-Crawler Seiten, ohne sie zu rendern.
JacquesB
2
Es gibt eine andere Vorstellung von der Wortseite: das Ergebnis eines Paginierungsprozesses, wenn eine HTML-Seite zum Drucken formatiert wurde, vielleicht das, was @mouviciel im Sinn hatte. In dieser Frage ist a pagejedoch eindeutig eine Eingabe für den Renderer, keine Ausgabe, zu der dieser Begriff eindeutig nicht passt.
Doc Brown
1

Der D-Teil von SOLID sagt

"Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen."

Also, zwischen Seite und Renderer, was ist eher eine stabile Abstraktion, weniger wahrscheinlich zu ändern, möglicherweise eine Schnittstelle darstellt? Welches ist im Gegensatz dazu das "Detail"?

Nach meiner Erfahrung ist die Abstraktion normalerweise der Renderer. Zum Beispiel könnte es ein einfacher Stream oder XML sein, sehr abstrakt und stabil. Oder ein ziemlich normales Layout. Ihre Seite ist eher ein benutzerdefiniertes Geschäftsobjekt, ein "Detail". Und Sie haben andere Geschäftsobjekte zu rendern, wie "Bilder", "Berichte", "Diagramme" usw. (Wahrscheinlich kein "Tryptich" wie in meinem Kommentar)

Aber es hängt natürlich von Ihrem Design ab. Die Seite kann abstrakt sein, z. B. das Äquivalent eines HTML- <article>Tags mit Standardunterteilen. Und Sie haben viele verschiedene benutzerdefinierte "Renderer" für Geschäftsberichte. In diesem Fall sollte der Renderer von der Seite abhängen.

user949300
quelle
0

Ich denke, die meisten Klassen können in zwei Kategorien unterteilt werden:

  • Klassen, die Daten enthalten (veränderlich oder unveränderlich, spielt keine Rolle)

Dies sind Klassen, die fast keine Abhängigkeiten von irgendetwas anderem haben. Sie sind normalerweise Teil Ihrer Domain. Sie sollten keine oder nur eine Logik enthalten, die direkt aus ihrem Zustand abgeleitet werden kann. Eine Employee-Klasse kann eine Funktion haben isAdult, die direkt von ihrer abgeleitet werden kann, birthDateaber keine Funktion hasBirthDay, die externe Informationen erfordert (das aktuelle Datum).

  • Klassen, die Dienste anbieten

Diese Arten von Klassen können für andere Klassen verwendet werden, die Daten enthalten. Sie sind in der Regel einmal konfiguriert und unveränderlich (sodass sie immer die gleiche Art von Funktion ausführen). Diese Arten von Klassen können jedoch immer noch eine zustandsbehaftete kurzlebige Hilfsinstanz bereitstellen, um komplexere Vorgänge auszuführen, bei denen ein bestimmter Zustand für einen kurzen Zeitraum beibehalten werden muss (z. B. Builder-Klassen).

Dein Beispiel

In Ihrem Beispiel Pagewäre dies eine Klasse, die Daten enthält. Es sollte Funktionen haben, um diese Daten abzurufen und möglicherweise zu ändern, wenn die Klasse veränderbar sein soll. Halten Sie es dumm, damit es ohne viele Abhängigkeiten verwendet werden kann.

Daten, oder in diesem Fall Ihre, Pagekönnten auf vielfältige Weise dargestellt werden. Es könnte als Webseite gerendert, auf eine Festplatte geschrieben, in einer Datenbank gespeichert und in JSON konvertiert werden. Sie möchten einer solchen Klasse für jeden dieser Fälle keine Methoden hinzufügen (und Abhängigkeiten von allen Arten anderer Klassen erstellen, obwohl Ihre Klasse nur Daten enthalten soll).

Ihr Rendererist eine typische Service-Typ-Klasse. Es kann einen bestimmten Datensatz verarbeiten und ein Ergebnis zurückgeben. Es hat nicht viel eigenen Status und welcher Status normalerweise unveränderlich ist, kann einmal konfiguriert und dann wiederverwendet werden.

Beispielsweise könnten Sie eine MobileRendererund eine haben StandardRenderer, beide Implementierungen der RendererKlasse, aber mit unterschiedlichen Einstellungen.

Da also PageDaten enthalten und stumm gehalten werden sollten, wäre die sauberste Lösung in diesem Fall, das Pagean a zu übergeben Renderer:

$renderer->renderPage($page)
john16384
quelle
2
Sehr prozedurale Logik.
user949300