2009-07-03 4 views
6

Hier ist die Situation: Ich habe eine Klasse, die zu viel macht. Es ist hauptsächlich für den Zugriff auf Konfigurationsinformationen, aber es hat auch die Datenbankverbindung. Es ist als Singleton implementiert, so dass auch Unit-Tests schwierig werden, da der meiste Code ziemlich eng damit verbunden ist. Dies ist noch problematischer, da es eine Import-Zeitabhängigkeit erzeugt (wir machen dies in Python), was bedeutet, dass bestimmte Module in einer bestimmten Reihenfolge importiert werden müssen. Idealerweise möchte ich beides in zwei Klassen aufteilen und es zu einem Nicht-Singleton machen.Was ist böser: ein unnötiger Singleton oder ein God Object?

Zum Glück hat sich mein Arbeitgeber der Tatsache erfreut, dass diese Art von Tests gut ist und ich erlaube mir, Änderungen wie diese vorzunehmen, wenn Code mehr testbar wird. Allerdings bezweifle ich, dass sie bereit sein werden, zu viel Zeit darauf zu verbringen. Und ich würde das lieber inkrementell beheben, anstatt zu versuchen, zu radikal zu sein.

Also, ich sehe drei Möglichkeiten hier:

  1. Pause des Konfigurationsobjekt in ein (Singleton) Konfigurationsobjekt und ein (nicht-Singleton) Datenbankobjekt. Dies würde mir erlauben, die Datenbank als Import-Zeit-Abhängigkeit zu entfernen.
  2. Machen Sie das Konfigurationsobjekt zu einem Nicht-Singleton und übergeben Sie es an Objekte, die es benötigen. Ich denke, dass dies unseren kurzfristigen Bedürfnissen besser gerecht wird, aber ich denke, dass es deutlich mehr Zeit in Anspruch nehmen würde.
  3. Tun Sie etwas, woran ich nicht gedacht hätte, was Sie in Ihrer Antwort vorschlagen. :-)

Also was mache ich?

Antwort

5

Ich denke, Sie sind auf dem richtigen Weg, um in zwei Klassen zu trennen. Möglicherweise möchten Sie eine Factory verwenden, um Ihren Datenbankkontext/Ihre Datenbankverbindung nach Bedarf zu erstellen. Auf diese Weise können Sie die Verbindung als eine Einheit der Arbeitsentität behandeln, die nach Bedarf erstellt/entsorgt wird, anstatt eine einzelne Verbindung für die Lebensdauer des Objekts zu halten. YMMV, obwohl.

Wie für die Konfiguration ist dies ein Fall, in dem ich finde, dass ein Singleton die richtige Wahl sein kann. Ich würde es nicht unbedingt ausgeben, nur weil es schwierig ist, den Test zu testen. Sie sollten jedoch erwägen, es zu erstellen, um eine Schnittstelle zu implementieren.Sie können dann die Abhängigkeitsinjektion verwenden, um beim Testen eine Scheininstanz der Schnittstelle bereitzustellen. Ihr Produktionscode würde entweder für die Verwendung der Singleton-Instanz erstellt, wenn der injizierte Wert Null ist, oder für die Singleton-Instanz. Alternativ können Sie die Klasse so konstruieren, dass die Reinitialisierung über eine private Methode möglich ist, und diese in den Testmethoden für Setup/Teardown aufrufen, um sicherzustellen, dass die Konfiguration für Ihre Tests korrekt ist. Ich ziehe die erste der letzteren Implementierung vor, obwohl ich sie auch verwendet habe, wenn ich keine direkte Kontrolle über die Schnittstelle habe.

Die Änderungen inkrementell vorzunehmen ist definitiv der richtige Weg. Wenn möglich, sollten Sie die aktuelle Funktionalität mit Tests versehen und sicherstellen, dass diese Tests auch nach Ihren Änderungen bestehen bleiben (natürlich nicht die, die direkt mit Ihren Änderungen zu tun haben), um sicherzustellen, dass Sie anderen Code nicht beschädigen .

5

Es ist schwer zu wissen, ohne Ihren Code zu sehen, aber warum nicht tun, was Sie sagen - tun Sie es inkrementell? Tun Sie zuerst Schritt 1, teilen Sie die Datenbank auf.

Wenn das schnell ist, dann gehen Sie zurück, und Sie haben jetzt nur noch ein kleineres Objekt, um aufzuhören, ein Singleton statt 2 zu sein. So sollte Schritt 2 schneller sein. Oder in diesem Stadium sehen Sie möglicherweise einen anderen Code, der aus dem Singleton heraus refaktoriert werden kann.

Hoffentlich können Sie Schritt für Schritt reduzieren, was im Singleton ist, bis es verschwindet, ohne jemals eine große Zeitsteuer in einem Schritt zu zahlen.

Zum Beispiel könnte vielleicht ein Teil der Konfiguration Singleton zu einem Zeitpunkt gemacht werden, wenn die Teile der Konfiguration unabhängig sind. Vielleicht hat die GUI-Konfiguration den Singleton verlassen, während die Dateikonfiguration refaktoriert wurde, oder etwas Ähnliches?

+0

Ich vermute, dass * ist * etwas, an das ich nicht gedacht hatte. Wenn man das Objekt aufbricht, wird es einfacher, das De-Singleton zu entfernen (wenn das ein Wort ist). –

+0

Wie wäre es mit Desingulieren? –

+1

oder vielleicht Pluralität? :-) –

2

Option 1 ist, was ich in allen meinen Apps mache: Konfigurationsobjekt Singleton und Datenbankobjekte, die auf Anforderung erstellt oder injiziert werden.

Das Cofigurationsobjekt als Singleton zu haben, war für mich immer eine perfekte Lösung. Es gibt nur eine Konfigurationsdatei und ich möchte sie immer lesen, wenn die Anwendung startet.

+0

Aber da haben wir Probleme. Wir möchten die Konfigurationsdatei beim Programmstart lesen. Wenn das Konfigurationsobjekt jedoch global ist, wie verhindert man, dass anderer Code versucht, darauf zuzugreifen, bevor er initialisiert wird? Ich finde, dass das viel schwieriger zu verhindern ist als es klingt. –

+0

Ich erreiche dies, indem ich jedes Property mache, auf das im Singleton-Static zugegriffen wird, und alle Initialisierungslogik der Konfigurationsklassen in seinen statischen Konstruktor einfügt. Das bedeutet, sobald der erste Versuch unternommen wird, auf eine Eigenschaft des Konfigurationsobjekts zuzugreifen, wird sie sich initialisieren. Ich verwende diesen Ansatz mit C# und bin mir nicht sicher, ob Python den gleichen Ansatz mit statischen Eigenschaften und Konstruktoren hat. – sipwiz

2

Excuse die Tatsache, dass ich eine Python weiß es nicht, so dass ich hoffe, dass jeder Pseudo-Code Sinn macht ...

ich zuerst das Objekt in zwei Teile geteilt würde, so dass die Verantwortlichkeit Ihrer Singleton kleiner sind , dann würde ich die restliche Konfiguration Singleton nehmen und sie in eine normale Klasse ändern (als ob du deinen zweiten Vorschlag machen würdest).

Einmal bekam ich würde so weit würde ich eine neue, Wrapper Singletons erstellen, die die Methoden der Konfigurationsklasse macht:

class ConfigurationWrapper : IConfigurationClass 
{ 
    public static property ConfigurationWrapper Instance; 

    public property IConfigurationClass InnerClass; 

    public method GetDefaultWindowWidth() 
    { 
     return InnerClass.GetDefaultWindowWidth(); 
    } 

    etc... 
} 

nun das erste, was die Anwendung tut, ist eine Instanz der injizieren ConfigurationClass in den Wrapper.

ConfigurationClass config = new ConfigurationClass() 
ConfigurationWrapper.Instance.InnerClass = config; 

Schließlich können Sie Klassen bewegen, die derzeit auf dem Singleton verlassen über Wrapper Singleton (die eine schnelle Entdeckung sein sollte und ersetzen). Jetzt können Sie die Klassen einzeln konvertieren, um das config-Objekt über ihre Konstruktoren zu übernehmen, um Stufe 2 zu beenden. Für alle Zeiten, für die Sie keine Zeit haben, können Sie das Wrapper-Singleton verwenden. Sobald Sie sie alle verschoben haben, können Sie den Wrapper loswerden.

Auf der anderen Seite können Sie die Refactoring der Verwendungen ignorieren und einfach eine mocked Konfigurationsklasse in den Singleton-Wrapper zum Testen injizieren - im Wesentlichen eine Armen Injektion der Abhängigkeit.