Betrachten Sie diese beiden Beispiele:
Übergeben eines Objekts an einen Konstruktor
class ExampleA
{
private $config;
public function __construct($config)
{
$this->config = $config;
}
}
$config = new Config;
$exampleA = new ExampleA($config);
Eine Klasse instanziieren
class ExampleB
{
private $config;
public function __construct()
{
$this->config = new Config;
}
}
$exampleA = new ExampleA();
Wie gehe ich beim Hinzufügen eines Objekts als Eigenschaft richtig vor? Wann sollte ich eins über das andere verwenden? Beeinflusst Unit-Tests, was ich verwenden soll?
object-oriented
unit-testing
configuration
Häftling
quelle
quelle
Antworten:
Ich denke, der erste gibt Ihnen die Möglichkeit, ein
config
Objekt an anderer Stelle zu erstellen und an dieses weiterzugebenExampleA
. Wenn Sie eine Abhängigkeitsinjektion benötigen, kann dies eine gute Sache sein, da Sie sicherstellen können, dass alle Intances dasselbe Objekt verwenden.Auf der anderen Seite
ExampleA
benötigen Sie möglicherweise ein neues und sauberesconfig
Objekt, sodass es Fälle geben kann, in denen das zweite Beispiel besser geeignet ist, z. B. Fälle, in denen jede Instanz möglicherweise eine andere Konfiguration hat.quelle
Vergessen Sie nicht die Testbarkeit!
Wenn das Verhalten der
Example
Klasse von der Konfiguration abhängt, möchten Sie sie normalerweise testen können, ohne den internen Status der Klasseninstanz zu speichern / zu ändern (dh Sie möchten einfache Tests für den Pfad "Happy Path" und für die falsche Konfiguration durchführen, ohne die Eigenschaft / zu ändern). Mitglied derExample
Klasse).Daher würde ich mit der ersten Option von gehen
ExampleA
quelle
Wenn das Objekt für die Verwaltung der Lebensdauer der Abhängigkeit verantwortlich ist, können Sie das Objekt im Konstruktor erstellen (und im Destruktor entsorgen). *
Wenn das Objekt nicht für die Verwaltung der Lebensdauer der Abhängigkeit verantwortlich ist, sollte es an den Konstruktor übergeben und von außen verwaltet werden (z. B. von einem IoC-Container).
In diesem Fall sollte Ihre ClassA meines Erachtens nicht die Verantwortung für die Erstellung von $ config übernehmen, es sei denn, sie ist auch für die Entsorgung verantwortlich oder die Konfiguration ist für jede Instanz von ClassA eindeutig.
* Um die Testbarkeit zu verbessern, kann der Konstruktor auf eine Factory-Klasse / -Methode verweisen, um die Abhängigkeit in seinem Konstruktor zu konstruieren, wodurch die Kohäsion und Testbarkeit erhöht wird.
quelle
Ich habe kürzlich genau die gleiche Diskussion mit unserem Architektenteam geführt, und es gibt einige subtile Gründe, dies auf die eine oder andere Weise zu tun. Es kommt hauptsächlich auf die Abhängigkeitsinjektion an (wie andere angemerkt haben) und darauf, ob Sie wirklich die Kontrolle über die Erstellung des Objekts haben, das Sie neu im Konstruktor haben.
Was ist in Ihrem Beispiel, wenn Ihre Config-Klasse:
a) hat eine nicht triviale Zuordnung, z. B. aus einem Pool oder einer Fabrikmethode.
b) könnte nicht zugewiesen werden. Durch die Übergabe an den Konstruktor wird dieses Problem vermieden.
c) ist eigentlich eine Unterklasse von Config.
Die Übergabe des Objekts an den Konstruktor bietet die größte Flexibilität.
quelle
Die Antwort unten ist falsch, aber ich werde sie behalten, damit andere daraus lernen können (siehe unten).
In
ExampleA
können Sie dieselbeConfig
Instanz für mehrere Klassen verwenden. Sollte es jedoch nur eineConfig
Instanz in der gesamten Anwendung geben, sollten Sie das Singleton-Muster anwendenConfig
, um zu vermeiden, dass mehrere Instanzen von vorhanden sindConfig
. Und wennConfig
es sich um einen Singleton handelt, können Sie stattdessen Folgendes tun:In
ExampleB
, auf der anderen Seite finden Sie immer eine separate Instanz erhaltenConfig
für jede InstanzExampleB
.Welche Version Sie wirklich anwenden sollten, hängt davon ab, wie die Anwendung Instanzen von Folgendem behandelt
Config
:ExampleX
eine separate Instanz von haben sollteConfig
, gehen Sie mitExampleB
;ExampleX
eine (und nur eine) Instanz von gemeinsamConfig
nutzt, verwenden SieExampleA with Config Singleton
;ExampleX
unterschiedliche Instanzen von verwenden könnenConfig
, bleiben Sie beiExampleA
.Warum die Konvertierung
Config
in einen Singleton falsch ist:Ich muss zugeben, dass ich erst gestern etwas über das Singleton- Muster gelernt habe (Lesen des Head First- Buches mit Designmustern). Naiv ging ich herum und wandte es für dieses Beispiel an, aber wie viele darauf hingewiesen haben, ist ein Weg ein anderer (einige waren kryptischer und sagten nur "Du machst es falsch!"), Dies ist keine gute Idee. Um zu verhindern, dass andere den gleichen Fehler machen, den ich gerade gemacht habe, folgt eine Zusammenfassung, warum das Singleton- Muster schädlich sein kann (basierend auf den Kommentaren und dem, was ich beim Googeln herausgefunden habe):
Wenn
ExampleA
ein eigener Verweis auf dieConfig
Instanz abgerufen wird , werden die Klassen eng gekoppelt. Es wird keine Möglichkeit geben, eine InstanzExampleA
zu haben, um eine andere Version vonConfig
(sagen wir einige Unterklassen) zu verwenden. Dies ist schrecklich, wenn SieExampleA
eine Mock-up-Instanz von testen möchten,Config
da es keine Möglichkeit gibt, sie bereitzustellenExampleA
.Die Prämisse davon wird es eine und nur eine Instanz geben, die
Config
vielleicht jetzt gilt , aber Sie können nicht immer sicher sein, dass dies auch in Zukunft der Fall sein wird . Wenn sich zu einem späteren Zeitpunkt herausstellt, dass mehrere Instanzen vonConfig
wünschenswert sind, gibt es keine Möglichkeit, dies zu erreichen, ohne den Code neu zu schreiben.Auch wenn die einzige Instanz von
Config
möglicherweise für alle Ewigkeit gilt, kann es vorkommen, dass Sie eine Unterklasse von verwenden möchtenConfig
(während Sie immer noch nur eine Instanz haben). Da der Code die Instanz jedoch direkt übergetInstance()
of abruftConfig
, was einestatic
Methode ist, gibt es keine Möglichkeit, die Unterklasse abzurufen. Auch hier muss der Code neu geschrieben werden.Die Tatsache, dass
ExampleA
verwendetConfig
wird, wird verborgen, zumindest wenn nur die API von angezeigt wirdExampleA
. Das mag eine schlechte Sache sein oder auch nicht, aber ich persönlich fühle, dass dies ein Nachteil ist. Bei der Wartung gibt es beispielsweise keine einfache Möglichkeit, herauszufinden, welche Klassen von Änderungen betroffen sind,Config
ohne die Implementierung jeder anderen Klasse zu untersuchen.Auch wenn die Tatsache, dass
ExampleA
ein Singleton verwendetConfig
wird, an sich kein Problem darstellt, kann es aus Testsicht immer noch zu einem Problem werden. Singleton- Objekte tragen den Status, der bis zur Beendigung der Anwendung bestehen bleibt. Dies kann ein Problem sein, wenn Unit-Tests ausgeführt werden, da ein Test von einem anderen isoliert werden soll (dh, dass die Ausführung eines Tests das Ergebnis eines anderen Tests nicht beeinflussen sollte). Um dies zu beheben, muss das Singleton- Objekt zwischen jedem Testlauf zerstört werden (möglicherweise muss die gesamte Anwendung neu gestartet werden), was zeitaufwändig sein kann (ganz zu schweigen von langwierig und ärgerlich).Trotzdem bin ich froh, dass ich diesen Fehler hier gemacht habe und nicht bei der Implementierung einer echten Anwendung. Tatsächlich dachte ich darüber nach, meinen neuesten Code neu zu schreiben, um das Singleton- Muster für einige der Klassen zu verwenden. Obwohl ich die Änderungen leicht rückgängig machen könnte (natürlich ist alles in einem SVN gespeichert), hätte ich trotzdem Zeit damit verschwendet.
quelle
ExampleA
undConfig
- was nicht gut ist.Die einfachste Sache zu tun ist , zu koppeln
ExampleA
zuConfig
. Sie sollten das Einfachste tun, es sei denn, es gibt einen zwingenden Grund, etwas Komplexeres zu tun.Ein Grund für die Entkopplung
ExampleA
undConfig
wäre die Verbesserung der Testbarkeit vonExampleA
. Direkte Kopplung wird die Prüfbarkeit degradieren ,ExampleA
wennConfig
hat Methoden , die langsam, komplex sind oder sich schnell entwickelnden. Zum Testen ist eine Methode langsam, wenn sie länger als einige Mikrosekunden läuft. Wenn alle MethodenConfig
sind einfach und schnell, dann würde ich den einfachen Ansatz nehmen und direkt PaarExampleA
zuConfig
.quelle
Ihr erstes Beispiel ist ein Beispiel für das Abhängigkeitsinjektionsmuster. Eine Klasse mit einer externen Abhängigkeit erhält die Abhängigkeit vom Konstruktor, Setter usw.
Dieser Ansatz führt zu lose gekoppeltem Code. Die meisten Leute denken, dass lose Kopplung eine gute Sache ist, da Sie die Konfiguration leicht ersetzen können, wenn eine bestimmte Instanz eines Objekts anders als die anderen konfiguriert werden muss. Sie können ein Scheinkonfigurationsobjekt zum Testen übergeben und so weiter auf.
Der zweite Ansatz ist näher am GRASP-Erstellungsmuster. In diesem Fall erstellt das Objekt seine eigenen Abhängigkeiten. Dies führt zu eng gekoppeltem Code. Dies kann die Flexibilität der Klasse einschränken und das Testen erschweren. Wenn Sie eine Instanz einer Klasse benötigen, um eine andere Abhängigkeit als die anderen zu haben, können Sie sie nur in Unterklassen unterteilen.
Dies kann jedoch das geeignete Muster sein, wenn die Lebensdauer des abhängigen Objekts durch die Lebensdauer des abhängigen Objekts bestimmt wird und das abhängige Objekt außerhalb des davon abhängigen Objekts nicht verwendet wird. Normalerweise würde ich DI raten, die Standardposition zu sein, aber Sie müssen den anderen Ansatz nicht vollständig ausschließen, solange Sie sich seiner Konsequenzen bewusst sind.
quelle
Wenn Ihre Klasse die nicht
$config
für externe Klassen verfügbar macht, würde ich sie im Konstruktor erstellen. Auf diese Weise halten Sie Ihren internen Status privat.Wenn die
$config
Anforderung erfordert, dass der eigene interne Status vor der Verwendung ordnungsgemäß festgelegt wird (z. B. eine Datenbankverbindung oder einige interne Felder initialisiert werden müssen), ist es sinnvoll, die Initialisierung auf einen externen Code (möglicherweise eine Factory-Klasse) zu verschieben und ihn einzufügen der Konstruktor. Oder, wie andere betont haben, wenn es unter anderen Objekten geteilt werden muss.quelle
Beispiel A ist von der konkreten Klasse Config entkoppelt, was gut ist , vorausgesetzt, das empfangene Objekt ist nicht vom Typ Config, sondern vom Typ einer abstrakten Superklasse von Config.
Beispiel B ist stark an die konkrete Klasse Config gekoppelt, was schlecht ist .
Das Instanziieren eines Objekts erzeugt eine starke Kopplung zwischen Klassen. Dies sollte in einer Factory-Klasse erfolgen.
quelle