Welcher Code sollte in einer abstrakten Klasse enthalten sein?

9

Ich bin in letzter Zeit besorgt über die Verwendung von abstrakten Klassen.

Manchmal wird im Voraus eine abstrakte Klasse erstellt, die als Vorlage für die Funktionsweise der abgeleiteten Klassen dient. Das bedeutet mehr oder weniger, dass sie einige Funktionen auf hoher Ebene bieten, aber bestimmte Details auslassen, die von abgeleiteten Klassen implementiert werden müssen. Die abstrakte Klasse definiert die Notwendigkeit dieser Details, indem sie einige abstrakte Methoden einführt. In solchen Fällen funktioniert eine abstrakte Klasse wie eine Blaupause, eine allgemeine Beschreibung der Funktionalität oder wie auch immer Sie sie nennen möchten. Es kann nicht alleine verwendet werden, sondern muss spezialisiert sein, um die Details zu definieren, die bei der Implementierung auf hoher Ebene nicht berücksichtigt wurden.

Manchmal kommt es vor, dass die abstrakte Klasse nach der Erstellung einiger "abgeleiteter" Klassen erstellt wird (da die übergeordnete / abstrakte Klasse nicht vorhanden ist, wurden sie noch nicht abgeleitet, aber Sie wissen, was ich meine). In diesen Fällen wird die abstrakte Klasse normalerweise als Ort verwendet, an dem Sie jede Art von allgemeinem Code einfügen können, den aktuell abgeleitete Klassen enthalten.

Nachdem ich die obigen Beobachtungen gemacht habe, frage ich mich, welcher dieser beiden Fälle die Regel sein sollte. Sollten irgendwelche Details in die abstrakte Klasse eingeblasen werden, nur weil sie derzeit in allen abgeleiteten Klassen gemeinsam sind? Sollte allgemeiner Code vorhanden sein, der nicht Teil einer High-Level-Funktionalität ist?

Sollte Code vorhanden sein, der für die abstrakte Klasse selbst möglicherweise keine Bedeutung hat, nur weil er zufällig für die abgeleiteten Klassen gleich ist?

Lassen Sie mich ein Beispiel geben: Die abstrakte Klasse A hat eine Methode a () und eine abstrakte Methode aq (). Die Methode aq () verwendet in beiden abgeleiteten Klassen AB und AC eine Methode b (). Sollte b () nach A verschoben werden? Wenn ja, dann würde die Existenz von b () nicht viel Sinn machen, wenn jemand nur A ansieht (tun wir so, als wären AB und AC nicht da)! Ist das eine schlechte Sache? Sollte jemand in der Lage sein, einen Blick in eine abstrakte Klasse zu werfen und zu verstehen, was vor sich geht, ohne die abgeleiteten Klassen zu besuchen?

Um ehrlich zu sein, glaube ich im Moment, dass das Schreiben einer abstrakten Klasse, die Sinn macht, ohne in die abgeleiteten Klassen schauen zu müssen, eine Frage von sauberem Code und sauberer Architektur ist. Ι Die Idee einer abstrakten Klasse, die sich wie ein Speicherauszug für jede Art von Code verhält, ist in allen abgeleiteten Klassen nicht üblich.

Was denkst / übst du?

Alexandros Gougousis
quelle
7
Warum muss es eine "Regel" geben?
Robert Harvey
1
Writing an abstract class that makes sense without having to look in the derived classes is a matter of clean code and clean architecture.-- Warum? Ist es nicht möglich, dass ich beim Entwerfen und Entwickeln einer Anwendung feststelle, dass mehrere Klassen gemeinsame Funktionen haben, die natürlich in eine abstrakte Klasse umgewandelt werden können? Muss ich hellsichtig genug sein, um dies immer vorwegzunehmen, bevor ich Code für abgeleitete Klassen schreibe? Wenn ich nicht so klug bin, darf ich dann kein solches Refactoring durchführen? Muss ich meinen Code rauswerfen und von vorne beginnen?
Robert Harvey
Entschuldigung, wenn ich missverstanden wurde! Ich habe versucht zu sagen, was ich (im Moment) als bessere Praxis empfinde, und nicht impliziert, dass es eine absolute Regel geben sollte. Darüber hinaus impliziere ich nicht, dass Code, der zur abstrakten Klasse gehört, nur im Voraus geschrieben werden sollte. Ich habe beschrieben, wie eine abstrakte Klasse in der Praxis zu Code auf hoher Ebene (der als Vorlage für die abgeleiteten Codes dient) sowie zu Code auf niedriger Ebene (den Sie nicht verstehen können, dass die Benutzerfreundlichkeit darin besteht, dass Sie nicht nachsehen) endet die abgeleiteten Klassen).
Alexandros Gougousis
@RobertHarvey Für eine öffentliche Basisklasse ist es Ihnen untersagt, abgeleitete Klassen anzuzeigen. Für interne Klassen macht es keinen Unterschied.
Frank Hileman

Antworten:

4

Lassen Sie mich ein Beispiel geben: Die abstrakte Klasse A hat eine Methode a () und eine abstrakte Methode aq (). Die Methode aq () verwendet in beiden abgeleiteten Klassen AB und AC eine Methode b (). Sollte b () nach A verschoben werden? Wenn ja, dann würde die Existenz von b () nicht viel Sinn machen, wenn jemand nur A ansieht (tun wir so, als wären AB und AC nicht da)! Ist das eine schlechte Sache? Sollte jemand in der Lage sein, einen Blick in eine abstrakte Klasse zu werfen und zu verstehen, was vor sich geht, ohne die abgeleiteten Klassen zu besuchen?

Was Sie fragen, ist, wo Sie platzieren sollen b(), und in einem anderen Sinne ist eine Frage, ob Adie beste Wahl als unmittelbare Superklasse für ABund ist AC.

Es scheint drei Möglichkeiten zu geben:

  1. lassen b()in beiden ABundAC
  2. Erstellen Sie eine Zwischenklasse ABAC-Parent, die von erbt Aund diese einführt b()und dann als unmittelbare Superklasse für ABund verwendet wirdAC
  3. setzen b()in A(nicht zu wissen , ob eine andere Zukunft Klasse ADwollen b()oder nicht)

  1. leidet darunter, nicht trocken zu sein .
  2. leidet an YAGNI .
  3. Also, das lässt diesen.

Bis sich eine andere Klasse AD, die nicht will, b()präsentiert, scheint (3) die richtige Wahl zu sein.

Zum Zeitpunkt eines ADGeschenks können wir dann den Ansatz in (2) umgestalten - schließlich handelt es sich um Software!

Erik Eidt
quelle
7
Es gibt eine 4. Option. Setzen Sie b()keine Klasse ein. Machen Sie es zu einer freien Funktion, die alle ihre Daten als Argumente verwendet und beides hat ABund sie ACaufruft. Dies bedeutet, dass Sie es beim Hinzufügen nicht verschieben oder weitere Klassen erstellen müssen AD.
user1118321
1
@ user1118321, ausgezeichnetes, nettes Denken über den Tellerrand hinaus. Sinnvoll, wenn b()kein instanzlanglebiger Zustand benötigt wird.
Erik Eidt
Was mich in (3) beunruhigt, ist, dass die abstrakte Klasse kein in sich geschlossenes Verhalten mehr beschreibt. Es ist ein Stückchen Code, und ich kann den abstrakten Klassencode nicht verstehen, ohne zwischen dieser und allen abgeleiteten Klassen hin und her zu gehen.
Alexandros Gougousis
Können Sie eine Basis / Standardeinstellung festlegen ac, die aufruft, banstatt acabstrakt zu sein?
Erik Eidt
3
Für mich ist die wichtigste Frage, WARUM sowohl AB als auch AC dieselbe Methode b () verwenden. Wenn es nur Zufall ist, würde ich zwei ähnliche Implementierungen in AB und AC belassen. Aber wahrscheinlich liegt es daran, dass es eine gemeinsame Abstraktion für AB und AC gibt. Für mich ist DRY nicht so sehr ein Wert an sich, sondern ein Hinweis darauf, dass Sie wahrscheinlich eine nützliche Abstraktion verpasst haben.
Ralf Kleberhoff
2

Eine abstrakte Klasse ist nicht als Abladeplatz für verschiedene Funktionen oder Daten gedacht, die in die abstrakte Klasse geworfen werden, da dies praktisch ist.

Die einzige Faustregel, die den zuverlässigsten und erweiterbarsten objektorientierten Ansatz zu bieten scheint, lautet: " Bevorzugen Sie die Komposition gegenüber der Vererbung ." Eine abstrakte Klasse wird wahrscheinlich am besten als Schnittstellenspezifikation angesehen, die keinen Code enthält.

Wenn Sie eine Methode haben, die eine Art Bibliotheksmethode ist, die mit der abstrakten Klasse einhergeht, eine Methode, die das wahrscheinlichste Mittel ist, um Funktionen oder Verhaltensweisen auszudrücken, die Klassen, die von einer abstrakten Klasse abgeleitet sind, normalerweise benötigen, ist es sinnvoll, eine zu erstellen Klasse zwischen der abstrakten Klasse und anderen Klassen, in denen diese Methode verfügbar ist. Diese neue Klasse bietet eine bestimmte Instanziierung der abstrakten Klasse, die eine bestimmte Implementierungsalternative oder einen bestimmten Implementierungspfad definiert, indem diese Methode bereitgestellt wird.

Die Idee einer abstrakten Klasse besteht darin, ein abstraktes Modell dessen zu haben, was die abgeleiteten Klassen, die die abstrakte Klasse tatsächlich implementieren, als Dienst oder Verhalten bereitstellen sollen. Vererbung ist leicht zu überbeanspruchen und so oft bestehen die nützlichsten Klassen aus verschiedenen Klassen unter Verwendung eines Mischmusters .

Es gibt jedoch immer die Änderungsfragen, was sich ändern wird und wie es sich ändern wird und warum es sich ändern wird.

Vererbung kann zu spröden und zerbrechlichen Quellen führen (siehe auch Vererbung: Verwenden Sie sie einfach nicht mehr! ).

Das fragile Basisklassenproblem ist ein grundlegendes Architekturproblem objektorientierter Programmiersysteme, bei denen Basisklassen (Superklassen) als "fragil" betrachtet werden, da scheinbar sichere Änderungen an einer Basisklasse, wenn sie von den abgeleiteten Klassen geerbt werden, zu Fehlfunktionen der abgeleiteten Klassen führen können . Der Programmierer kann nicht einfach feststellen, ob eine Änderung der Basisklasse sicher ist, indem er die Methoden der Basisklasse isoliert untersucht.

Richard Chambers
quelle
Ich bin sicher, dass jedes OOP-Konzept zu Problemen führen kann, wenn es missbraucht oder überbeansprucht oder missbraucht wird! Die Annahme, dass b () eine Bibliotheksmethode ist (z. B. eine reine Funktion ohne andere Abhängigkeiten), schränkt den Umfang meiner Frage ein. Vermeiden wir das.
Alexandros Gougousis
@AlexandrosGougousis Ich gehe nicht davon aus, dass dies b()eine reine Funktion ist. Es könnte ein Funktoid oder eine Vorlage oder etwas anderes sein. Ich verwende den Ausdruck "Bibliotheksmethode" als eine Komponente, ob reine Funktion, COM-Objekt oder was auch immer, das für die Funktionalität verwendet wird, die es für die Lösung bereitstellt.
Richard Chambers
Entschuldigung, wenn ich nicht klar war! Ich habe die reine Funktion als eines von vielen Beispielen erwähnt (Sie haben mehr gegeben).
Alexandros Gougousis
1

Ihre Frage schlägt einen Entweder-Oder-Ansatz für abstrakte Klassen vor. Aber ich denke, dass Sie sie als ein weiteres Werkzeug in Ihrer Toolbox betrachten sollten. Und dann stellt sich die Frage: Für welche Jobs / Probleme sind abstrakte Klassen das richtige Werkzeug?

Ein ausgezeichneter Anwendungsfall ist die Implementierung des Template-Methodenmusters . Sie fügen die gesamte invariante Logik in die abstrakte Klasse und die Variantenlogik in ihre Unterklassen ein. Beachten Sie, dass die gemeinsam genutzte Logik an sich unvollständig und nicht funktionsfähig ist. Meistens geht es darum, einen Algorithmus zu implementieren, bei dem mehrere Schritte immer gleich sind, aber mindestens ein Schritt variiert. Setzen Sie diesen einen Schritt als abstrakte Methode ein, die von einer der Funktionen innerhalb der abstrakten Klasse aufgerufen wird.

Manchmal wird im Voraus eine abstrakte Klasse erstellt, die als Vorlage für die Funktionsweise der abgeleiteten Klassen dient. Das bedeutet mehr oder weniger, dass sie einige Funktionen auf hoher Ebene bieten, aber bestimmte Details auslassen, die von abgeleiteten Klassen implementiert werden müssen. Die abstrakte Klasse definiert die Notwendigkeit dieser Details, indem sie einige abstrakte Methoden einführt. In solchen Fällen funktioniert eine abstrakte Klasse wie eine Blaupause, eine allgemeine Beschreibung der Funktionalität oder wie auch immer Sie sie nennen möchten. Es kann nicht alleine verwendet werden, sondern muss spezialisiert sein, um die Details zu definieren, die bei der Implementierung auf hoher Ebene nicht berücksichtigt wurden.

Ich denke, Ihr erstes Beispiel ist im Wesentlichen eine Beschreibung des Musters der Vorlagenmethode (korrigieren Sie mich, wenn ich falsch liege), daher würde ich dies als einen vollkommen gültigen Anwendungsfall für abstrakte Klassen betrachten.

Manchmal kommt es vor, dass die abstrakte Klasse nach der Erstellung einiger "abgeleiteter" Klassen erstellt wird (da die übergeordnete / abstrakte Klasse nicht vorhanden ist, wurden sie noch nicht abgeleitet, aber Sie wissen, was ich meine). In diesen Fällen wird die abstrakte Klasse normalerweise als Ort verwendet, an dem Sie jede Art von allgemeinem Code einfügen können, den aktuell abgeleitete Klassen enthalten.

Für Ihr zweites Beispiel denke ich, dass die Verwendung abstrakter Klassen nicht die optimale Wahl ist, da es überlegene Methoden für den Umgang mit gemeinsam genutzter Logik und dupliziertem Code gibt. Angenommen, Sie haben eine abstrakte Klasse A, abgeleitete Klassen Bund Cbeide abgeleiteten Klassen teilen eine Logik in Form einer Methode s(). Um den richtigen Ansatz zu finden, um die Duplizierung zu beseitigen, ist es wichtig zu wissen, ob die Methode s()Teil der öffentlichen Schnittstelle ist oder nicht.

Wenn dies nicht b()der Fall ist (wie in Ihrem eigenen konkreten Beispiel mit Methode ), ist der Fall recht einfach. Erstellen Sie einfach eine separate Klasse daraus und extrahieren Sie den Kontext, der zum Ausführen der Operation erforderlich ist. Dies ist ein klassisches Beispiel für Komposition über Vererbung . Wenn wenig oder kein Kontext benötigt wird, reicht möglicherweise bereits eine einfache Hilfsfunktion aus, wie in einigen Kommentaren vorgeschlagen.

Wenn s()es Teil der öffentlichen Schnittstelle ist, wird es etwas schwieriger. Unter der Annahme, dass s()nichts damit zu tun hat Bund Cdamit verwandt ist A, sollten Sie nicht s()hineinstecken A. Wo soll es dann hingelegt werden? Ich würde dafür argumentieren, eine separate Schnittstelle zu deklarieren I, die definiert s(). Andererseits sollten Sie eine separate Klasse erstellen, die die Logik für die Implementierung s()und beides enthält Bund davon Cabhängt.

Zuletzt finden Sie hier einen Link zu einer hervorragenden Antwort auf eine interessante Frage zu SO, die Ihnen bei der Entscheidung helfen kann, wann Sie sich für eine Schnittstelle und wann für eine abstrakte Klasse entscheiden:

Eine gute abstrakte Klasse reduziert die Menge an Code, die neu geschrieben werden muss, da die Funktionalität oder der Status gemeinsam genutzt werden können.

Larsbe
quelle
Ich würde zustimmen, dass s () als Teil der öffentlichen Schnittstelle die Dinge viel schwieriger macht. Ich würde im Allgemeinen vermeiden, öffentliche Methoden zu definieren, die andere öffentliche Methoden in derselben Klasse aufrufen.
Alexandros Gougousis
1

Mir scheint, Sie vermissen den Punkt der Objektorientierung, sowohl logisch als auch technisch. Sie beschreiben zwei Szenarien: Gruppieren des allgemeinen Verhaltens von Typen in einer Basisklasse und Polymorphismus. Dies sind beide legitime Anwendungen einer abstrakten Klasse. Ob Sie die Klasse abstrakt machen sollten oder nicht, hängt jedoch von Ihrem analytischen Modell ab, nicht von den technischen Möglichkeiten.

Sie sollten einen Typ erkennen, der in der realen Welt keine Inkarnationen hat, aber die Grundlage für bestimmte Typen bildet, die existieren. Beispiel: ein Tier. Es gibt kein Tier. Es ist immer ein Hund oder eine Katze oder was auch immer, aber es gibt kein echtes Tier. Doch Animal umrahmt sie alle.

Dann sprichst du von Levels. Levels sind nicht Teil des Geschäfts, wenn es um Vererbung geht. Auch das Erkennen gemeinsamer Daten oder gemeinsamen Verhaltens ist kein technischer Ansatz, der wahrscheinlich nicht helfen wird. Sie sollten ein Stereotyp erkennen und dann die Basisklasse einfügen. Wenn es kein solches Stereotyp gibt, sind einige Schnittstellen, die von mehreren Klassen implementiert werden, möglicherweise besser geeignet.

Der Name Ihrer abstrakten Klasse zusammen mit ihren Mitgliedern sollte sinnvoll sein. Es muss sowohl technisch als auch logisch unabhängig von abgeleiteten Klassen sein. Wie Animal könnte eine abstrakte Methode Eat (die polymorph wäre) und boolesche abstrakte Eigenschaften IsPet und IsLifestock haben, die Sinn macht, ohne über Katzen, Hunde oder Schweine Bescheid zu wissen. Jede Abhängigkeit (technisch oder logisch) sollte nur in eine Richtung gehen: vom Nachkommen zur Basis. Basisklassen selbst sollten auch keine Kenntnisse über absteigende Klassen haben.

Martin Maat
quelle
Das Stereotyp, das Sie erwähnen, ist mehr oder weniger dasselbe, was ich meine, wenn ich über Funktionen auf höherer Ebene spreche oder wenn jemand anderes über verallgemeinerte Konzepte spricht, die von Klassen beschrieben werden, die in der Hierarchie höher sind (im Vergleich zu spezielleren Konzepten, die von Klassen beschrieben werden, die in der Hierarchie niedriger sind Hierarchie).
Alexandros Gougousis
"Basisklassen selbst sollten auch keine Kenntnisse über absteigende Klassen haben." Dem stimme ich voll und ganz zu. Eine übergeordnete Klasse (abstrakt oder nicht) sollte eine in sich geschlossene Geschäftslogik enthalten, die nicht überprüft werden muss, um die Implementierung der untergeordneten Klasse zu verstehen.
Alexandros Gougousis
Es gibt noch etwas anderes an einer Basisklasse, die ihre Untertypen kennt. Immer wenn ein neuer Subtyp entwickelt wird, muss die Basisklasse geändert werden, die rückwärts ist und gegen das Open-Closed-Prinzip verstößt. Ich habe dies kürzlich in vorhandenem Code festgestellt. Eine Basisklasse hatte eine statische Methode, mit der Instanzen von Untertypen basierend auf der Konfiguration der Anwendung erstellt wurden. Die Absicht war ein Fabrikmuster. Auf diese Weise wird auch die SRP verletzt. Mit einigen SOLID-Punkten dachte ich immer "duh, das ist offensichtlich" oder "die Sprache wird sich automatisch darum kümmern", aber ich fand, dass die Leute kreativer sind, als ich mir vorstellen kann.
Martin Maat
0

Erster Fall aus Ihrer Frage:

In solchen Fällen funktioniert eine abstrakte Klasse wie eine Blaupause, eine allgemeine Beschreibung der Funktionalität oder wie auch immer Sie sie nennen möchten.

Zweiter Fall:

Manchmal ... wird die abstrakte Klasse normalerweise als Ort verwendet, an dem Sie jede Art von allgemeinem Code einfügen können, den aktuell abgeleitete Klassen enthalten.

Dann Ihre Frage :

Ich frage mich, welcher dieser beiden Fälle die Regel sein sollte. ... Sollte Code vorhanden sein, der für die abstrakte Klasse selbst möglicherweise keine Bedeutung hat, nur weil er zufällig für die abgeleiteten Klassen gleich ist?

IMO haben Sie zwei verschiedene Szenarien, daher können Sie keine einzige Regel zeichnen, um zu entscheiden, welches Design angewendet werden muss.

Für den ersten Fall haben wir Dinge wie das Muster der Vorlagenmethode , die bereits in anderen Antworten erwähnt wurden.

Für den zweiten Fall haben Sie das Beispiel einer abstrakten Klasse A angegeben, die die ab () -Methode empfängt. IMO sollte die b () -Methode nur verschoben werden, wenn dies für ALLE anderen abgeleiteten Klassen sinnvoll ist. Mit anderen Worten, wenn Sie es verschieben, weil es an zwei Stellen verwendet wird, ist dies wahrscheinlich keine gute Wahl, da es morgen möglicherweise eine neue konkrete abgeleitete Klasse gibt, für die b () überhaupt keinen Sinn ergibt. Wie Sie bereits sagten, ist es wahrscheinlich auch ein Hinweis darauf, dass dies keine gute Wahl für das Design ist, wenn Sie die Klasse A separat betrachten und b () in diesem Zusammenhang ebenfalls keinen Sinn ergibt.

Emerson Cardoso
quelle
-1

Ich habe vor einiger Zeit genau die gleichen Fragen gehabt!

Wenn ich auf dieses Problem stoße, stelle ich fest, dass es ein verstecktes Konzept gibt, das ich nicht gefunden habe. Und dieses Konzept sollte höchstwahrscheinlich mit einem Wertobjekt ausgedrückt werden . Sie haben ein sehr abstraktes Beispiel, daher kann ich leider nicht demonstrieren, was ich mit Ihrem Code meine. Aber hier ist ein Fall aus meiner eigenen Praxis. Ich hatte zwei Klassen, die eine Möglichkeit darstellten, eine Anfrage an eine externe Ressource zu senden und die Antwort zu analysieren. Es gab Ähnlichkeiten darin, wie die Anfragen gebildet wurden und wie die Antworten analysiert wurden. So sah meine Hierarchie aus:

abstract class AbstractProtocol
{
    /**
     * @return array Registration params to send
     */
    abstract protected function assembleRegistrationPart();

    /**
     * @return array Payment params to send
     */
    abstract protected function assemblePaymentPart();

    protected function doSend(array $data)
    {
        return
            (new HttpClient(
                [
                    'timeout' => 60,
                    'encoding' => 'utf-8',
                    'language' => 'en',
                ]
            ))
                ->send($data);
    }

    protected function log(array $data)
    {
        $header = 'Here is a request to external system!';
        $body = implode(', ', $this->maskData($data));
        Logger::log($header . '. \n ' . $body);
    }
}

class ClassicProtocol extends AbstractProtocol
{
    public function send()
    {
        $registration = $this->assembleRegistrationPart();
        $payment = $this->assemblePaymentPart();
        $specificParams = $this->assembleClassicSpecificPart();

        $dataToSend =
            array_merge(
                $registration, $payment, $specificParams
            );

        $this->log($dataToSend);

        $this->doSend($dataToSend);
    }

    protected function assembleRegistrationPart()
    {
        return ['hello' => 'there'];
    }

    protected function assemblePaymentPart()
    {
        return ['pay' => 'yes'];
    }
}

Ein solcher Code zeigt an, dass ich die Vererbung einfach missbrauche. So könnte es umgestaltet werden:

class ClassicProtocol
{
    private $request;
    private $logger;

    public function __construct(Request $request, Logger $logger, Client $client)
    {
        $this->request = $request;
        $this->client = $client;
        $this->logger = $logger;
    }

    public function send()
    {
        $this->logger->log($this->request->getData());
        $this->client->send($this->request->getData());
    }
}

$protocol =
    new ClassicProtocol(
        new PaymentRequest(
            new RegistrationData(),
            new PaymentData(),
            new ClassicSpecificData()
        ),
        new ClassicLogger(),
        new ClassicClient()
    );

class RegistrationData
{
    public function getData()
    {
        return ['hello' => 'there'];
    }
}

class PaymentData
{
    public function getData()
    {
        return ['pay' => 'yes'];
    }
}

class ClassicLogger
{
    public function log(array $data)
    {
        $header = 'Here is a request to external system!';
        $body = implode(', ', $this->maskData($data));
        Logger::log($header . '. \n ' . $body);
    }
}
class ClassicClient
{
    private $properties;

    public function __construct()
    {
        $this->properties =
            [
                'timeout' => 60,
                'encoding' => 'utf-8',
                'language' => 'en',
            ];
    }
}

Seitdem behandle ich die Erbschaft sehr sorgfältig, weil ich viele Male verletzt wurde.

Seitdem bin ich zu einem anderen Schluss über die Vererbung gekommen. Ich bin stark gegen Vererbung aufgrund der internen Struktur . Es bricht die Kapselung, es ist zerbrechlich, es ist schließlich prozedural. Und wenn ich meine Domain richtig zerlege , kommt die Vererbung einfach nicht sehr oft vor.

Vadim Samokhin
quelle
@Downvoter, bitte tu mir einen Gefallen und kommentiere, was mit dieser Antwort falsch ist.
Vadim Samokhin