2010-04-18 22 views
57

Wie identifizieren wir, wann die Abhängigkeitsinjektion oder das Singleton-Muster verwendet werden soll. Ich habe viele Websites gelesen, wo sie sagen "Verwenden Sie Dependency Injection über Singleton-Muster". Aber ich bin mir nicht sicher, ob ich ihnen völlig zustimme. Für meine kleinen oder mittleren Projekte sehe ich definitiv die Verwendung von Singleton-Muster direkt.Dependency Injection & Singleton Entwurfsmuster

Zum Beispiel Logger. Ich könnte verwenden Logger.GetInstance().Log(...) Aber stattdessen, warum muss ich jede Klasse, die ich erstelle, mit der Instanz des Loggers? Injizieren.

Antwort

50

Wenn Sie überprüfen möchten, was bei einem Test eingeloggt ist, benötigen Sie die Abhängigkeitsinjektion. Darüber hinaus ist ein Logger selten ein Singleton - in der Regel haben Sie einen Logger für jede Klasse.

Watch this presentation auf Objektorientierte Design für Testbarkeit und Sie werden sehen, warum Singletons schlecht sind.

Das Problem mit Singletons ist, dass sie einen globalen Zustand darstellen, der insbesondere in Tests schwer vorherzusagen ist.

Bedenken Sie, dass ein Objekt de facto Singleton sein kann, aber dennoch über die Abhängigkeitsinjektion erhalten werden kann, anstatt über Singleton.getInstance().

Ich liste nur einige wichtige Punkte auf, die Misko Hevery in seiner Präsentation gemacht hat. Nach dem Betrachten erhalten Sie volle Perspektive darüber, warum es besser ist, ein Objekt zu definieren was seine Abhängigkeiten sind, aber nicht einen Weg definieren wie man sie erstellen.

+0

Ich bin neu zu diesem Design pattren, und ich würde gerne wissen, warum meinst du mit "Test", wenn Sie speziell in Tests sagen? Danke im Voraus. – AnixPasBesoin

+0

Ich meine Unit/Integration Tests – Bozho

7

Es ist meistens, aber nicht vollständig über Tests. Singltons waren populär, weil es leicht war, sie zu konsumieren, aber es gibt eine Reihe von Nachteilen für Singletons.

  • Schwer zu testen. Bedeutung wie kann ich sicherstellen, dass der Logger richtig macht.
  • Schwer zu testen mit. Bedeutung, wenn ich Code tue, der den Logger benutzt, aber es nicht der Fokus meines Tests ist, muss ich noch sicherstellen, dass mein Test env den Logger
  • unterstützt Manchmal möchten Sie nicht ein singlton, aber mehr Flexibilität

DI gibt Ihnen den einfachen Verbrauch Ihrer abhängigen Klassen - setzen Sie es einfach in die Konstruktorargumente, und das System stellt es für Sie bereit - und gibt Ihnen gleichzeitig die Flexibilität beim Testen und Konstruieren.

+0

Nicht wirklich. Es geht um den gemeinsamen veränderlichen Zustand und statische Abhängigkeiten, die auf lange Sicht schmerzhaft sind. Testen ist nur das offensichtliche und häufig das schmerzhafteste Beispiel. – kyoryu

72

Singletons sind wie Kommunismus: Beide klingen auf dem Papier gut, explodieren aber mit Problemen in der Praxis.

Das Singleton-Muster legt einen unverhältnismäßigen Schwerpunkt auf die Leichtigkeit des Zugriffs auf Objekte. Es vermeidet vollständig den Kontext, indem es erfordert, dass jeder Verbraucher ein AppDomain-spezifisches Objekt verwendet und keine Optionen für unterschiedliche Implementierungen übrig lässt. Es bettet Infrastrukturwissen in Ihre Klassen ein (der Aufruf an GetInstance()), während es genau zero Ausdruckskraft hinzufügt. Es verringert tatsächlich Ihre Ausdruckskraft, weil Sie die von einer Klasse verwendete Implementierung nicht ändern können, ohne sie für alle von ihnen zu ändern. Sie können einfach keine einmaligen Funktionen hinzufügen.

Auch wenn Klasse Foo von Logger.GetInstance() abhängt, versteckt Foo effektiv seine Abhängigkeiten von den Verbrauchern.Dies bedeutet, dass Sie Foo nicht vollständig verstehen oder es mit Zuversicht verwenden können, es sei denn, Sie lesen die Quelle und decken die Tatsache auf, dass sie von Logger abhängt. Wenn Sie die Quelle nicht haben, begrenzt das, wie gut Sie den Code verstehen und effektiv nutzen können, von dem Sie abhängig sind.

Das Singleton-Muster, wie es mit statischen Eigenschaften/Methoden implementiert ist, ist wenig mehr als ein Hack rund um die Implementierung einer Infrastruktur. Es begrenzt Sie in unzähligen Arten und bietet keinen erkennbaren Nutzen gegenüber den Alternativen. Sie können es verwenden, wie Sie möchten, aber da es praktikable Alternativen gibt, die ein besseres Design fördern, sollte es niemals eine empfohlene Praxis sein.

+5

@BryanWatts alles, was gesagt wird, Singletons sind immer noch viel schneller und weniger fehleranfällig in einer normalen Anwendung im mittleren Maßstab. Normalerweise habe ich nicht mehr als eine mögliche Implementierung für einen (benutzerdefinierten) Logger, also warum müsste ich **: 1. Erstellen Sie eine Schnittstelle dafür und ändern Sie sie jedes Mal, wenn ich öffentliche Mitglieder hinzufügen/ändern muss. 2. Pflegen eine DI-Konfiguration 3. Verstecke die Tatsache, dass es ein einzelnes Objekt dieses Typs im gesamten System gibt 4. Verbinde mich mit einer strikten Funktionalität, die durch eine vorzeitige Trennung von Bedenken verursacht wird. –

+0

@UriAbramson: Es scheint, dass Sie die Entscheidung über die Kompromisse getroffen haben, die Sie bevorzugen, also werde ich nicht versuchen, Sie zu überzeugen. –

+0

@BryanWatts Es ist nicht der Fall, vielleicht war mein Kommentar ein bisschen zu hart, aber es interessiert mich wirklich sehr, was du über den Punkt zu sagen hast, den ich angesprochen habe ... –

13

Andere haben das Problem mit Singletons im Allgemeinen sehr gut erklärt. Ich möchte nur eine Notiz über den speziellen Fall von Logger hinzufügen. Ich stimme Ihnen zu, dass es in der Regel kein Problem ist, über eine statische getInstance() oder getRootLogger() Methode auf einen Logger (oder den Root Logger, um genau zu sein) als Singleton zuzugreifen. (es sei denn, Sie wollen sehen, was von der Klasse, die Sie testen, protokolliert wird - aber nach meiner Erfahrung kann ich mich kaum an solche Fälle erinnern, in denen dies notwendig war. Für andere wiederum könnte dies ein dringenderes Anliegen sein).

IMO normalerweise ein Singleton-Logger ist keine Sorge, da es keinen Zustand für die Klasse enthält, die Sie testen. Das heißt, der Zustand des Loggers (und seine möglichen Änderungen) haben keinerlei Auswirkung auf den Zustand der getesteten Klasse. Das macht deine Unit-Tests nicht schwieriger.

Die Alternative wäre, den Logger über den Konstruktor zu (fast) jeder einzelnen Klasse in Ihrer App zu injizieren. Für die Konsistenz der Schnittstellen sollte es injiziert werden, selbst wenn die fragliche Klasse derzeit nichts protokolliert - die Alternative wäre, dass Sie einen Logger benötigen, wenn Sie an einem Punkt feststellen, dass Sie jetzt benötigen, um etwas aus dieser Klasse zu protokollieren , daher müssen Sie einen Konstruktorparameter für DI hinzufügen, der den gesamten Clientcode unterbricht. Ich mag beide Optionen nicht, und ich denke, dass die Verwendung von DI für das Logging mein Leben nur verkomplizieren würde, um einer theoretischen Regel zu entsprechen, ohne irgendeinen konkreten Vorteil.

Also meine Quintessenz ist: eine Klasse, die (fast) universell verwendet wird, aber nicht Status relevant für Ihre App enthält, kann als Singleton sicher implementiert werden.

+1

Selbst dann kann ein Singleton ein Schmerz sein. Wenn Sie feststellen, dass Ihr Logger in einigen Fällen zusätzliche Parameter benötigt, können Sie entweder eine neue Methode erstellen (und die Logger-Funktion hässlicher machen) oder alle Logger-Benutzer unterbinden, selbst wenn es ihnen egal ist. – kyoryu

+2

@kyoryu, ich spreche über den "üblichen" Fall, der IMHO die Verwendung eines (De-facto) Standard-Logging-Framework impliziert. (Die typischerweise über Property/XML-Dateien btw konfigurierbar sind.) Natürlich gibt es Ausnahmen - wie immer. Wenn ich weiß, dass meine App in dieser Hinsicht außergewöhnlich ist, werde ich keinen Singleton verwenden.Aber Overengineering zu sagen, "das könnte manchmal nützlich sein" ist fast immer eine verschwendete Anstrengung. –

+0

Wenn Sie bereits DI verwenden, ist es nicht viel extra Engineering. (Übrigens, ich stimme dir nicht zu, ich bin der Upvote). Viele Logger benötigen einige "Kategorie" -Informationen oder etwas Ähnliches, und das Hinzufügen zusätzlicher Parameter kann Schmerzen verursachen. Das Verbergen hinter einer Schnittstelle kann dazu beitragen, den Code sauber zu halten und den Wechsel zu einem anderen Protokollierungsrahmen zu erleichtern. – kyoryu

1

Etwa die einzige Zeit, die Sie jemals ein Singleton anstelle von Dependency Injection verwenden sollten, wenn das Singleton einen unveränderlichen Wert darstellt, wie List.Empty oder dergleichen (unter der Annahme unveränderlicher Listen).

Die Darm-Prüfung für ein Singleton sollte "wäre ich in Ordnung, wenn dies eine globale Variable anstelle eines Singleton wäre?" Wenn nicht, verwenden Sie das Singleton-Muster zum Überschreiben einer globalen Variablen und sollten einen anderen Ansatz in Betracht ziehen.

1

geprüft einfach den Monostate Artikel aus - es ist eine nette Alternative zu Singleton, aber es hat einige seltsame Eigenschaften:

class Mono{ 
    public static $db; 
    public function setDb($db){ 
     self::$db = $db; 
    } 

} 

class Mapper extends Mono{ 
    //mapping procedure 
    return $Entity; 

    public function save($Entity);//requires database connection to be set 
} 

class Entity{ 
public function save(){ 
    $Mapper = new Mapper(); 
    $Mapper->save($this);//has same static reference to database class  
} 

$Mapper = new Mapper(); 
$Mapper->setDb($db); 

$User = $Mapper->find(1); 
$User->save(); 

Ist das nicht irgendwie beängstigend - weil der Mapper wirklich von der Datenbankverbindung abhängig ist, um save() auszuführen - aber wenn ein anderer Mapper wurde zuvor erstellt - es kann überspringen diesen Schritt beim Erwerb seiner Abhängigkeiten. Während ordentlich, es ist auch irgendwie chaotisch, nicht wahr?