Angenommen, wir haben 1001 Clients, die ihre Abhängigkeiten direkt aufbauen, anstatt Injektionen zu akzeptieren. Das Refactoring des 1001 ist laut unserem Chef keine Option. Wir haben nicht einmal Zugriff auf ihre Quelle, nur auf die Klassendateien.
Wir sollen das System, das diese 1001 Clients durchlaufen, "modernisieren". Wir können alles umgestalten, was uns gefällt. Die Abhängigkeiten sind Teil dieses Systems. Und einige dieser Abhängigkeiten sollen sich ändern, um eine neue Implementierung zu erhalten.
Wir möchten die Möglichkeit haben, verschiedene Implementierungen von Abhängigkeiten zu konfigurieren, um diese Vielzahl von Clients zu befriedigen. Leider scheint DI keine Option zu sein, da die Kunden keine Injektionen mit Konstruktoren oder Setzern akzeptieren.
Die Optionen:
1) Überarbeiten Sie die Implementierung des Dienstes, den die Clients verwenden, so, dass er das tut, was die Clients jetzt benötigen. Bang wir sind fertig. Nicht flexibel Nicht komplex
2) Umgestalten Sie die Implementierung so, dass sie ihre Arbeit an eine weitere Abhängigkeit delegiert, die sie über eine Fabrik erwirbt. Jetzt können wir steuern, welche Implementierung sie alle verwenden, indem wir die Fabrik umgestalten.
3) Umgestalten Sie die Implementierung so, dass sie ihre Arbeit an eine weitere Abhängigkeit delegiert, die sie über einen Service-Locator erhält. Jetzt können wir steuern, welche Implementierung sie alle verwenden, indem wir den Service-Locator konfigurieren, der möglicherweise nur hashmap
aus Zeichenfolgen für Objekte besteht, für die ein wenig Casting ausgeführt wird.
4) Daran habe ich noch gar nicht gedacht.
Das Ziel:
Minimieren Sie den Designschaden, der durch das Ziehen des alten, schlecht gestalteten Client-Codes in die Zukunft verursacht wird, ohne unnötige Komplexität hinzuzufügen.
Kunden sollten die Implementierung ihrer Abhängigkeiten nicht kennen oder kontrollieren, aber sie bestehen darauf, sie mit zu erstellen new
. Wir können nicht kontrollieren, new
aber wir kontrollieren die Klasse, die sie bauen.
Meine Frage:
Was habe ich nicht berücksichtigt?
Benötigen Sie wirklich eine Konfigurationsmöglichkeit zwischen verschiedenen Implementierungen? Für welchen Zweck?
Beweglichkeit. Viele Unbekannte. Management will Veränderungspotenzial. Nur die Abhängigkeit von der Außenwelt verlieren. Auch testen.
Benötigen Sie eine Laufzeitmechanik oder nur eine Kompilierzeitmechanik, um zwischen verschiedenen Implementierungen zu wechseln? Warum?
Kompilierzeitmechanik ist wahrscheinlich genug. Außer zum Testen.
Welche Granularität benötigen Sie, um zwischen den Implementierungen zu wechseln? Alles auf einmal? Pro Modul (jeweils mit einer Gruppe von Klassen)? Pro Klasse?
Von den 1001 wird jeweils nur einer durch das System geführt. Ändern, was alle Clients gleichzeitig verwenden, ist wahrscheinlich in Ordnung. Eine individuelle Kontrolle der Abhängigkeiten ist jedoch wahrscheinlich wichtig.
Wer muss den Schalter steuern? Nur dein / dein Entwicklerteam? Ein Administrator? Jeder Kunde für sich? Oder die Wartungsentwickler für den Client-Code? Wie einfach / robust / kinderleicht muss die Mechanik sein?
Dev zum Testen. Administrator als externe Hardware-Abhängigkeiten ändern. Das Testen und Konfigurieren muss einfach sein.
Unser Ziel ist es zu zeigen, dass das System schnell überarbeitet und modernisiert werden kann.
tatsächlicher Anwendungsfall für den Implementierungswechsel?
Zum einen werden einige Daten von der Software bereitgestellt, bis die Hardwarelösung fertig ist.
quelle
Antworten:
Nun, ich bin nicht sicher, ob ich die technischen Details und die genauen Unterschiede Ihrer unterstützten Lösungen vollständig verstehe, aber IMHO müssen Sie zuerst herausfinden, welche Art von Flexibilität Sie wirklich benötigen.
Die Fragen, die Sie sich stellen müssen, sind:
Benötigen Sie wirklich eine Konfigurationsmöglichkeit zwischen verschiedenen Implementierungen? Für welchen Zweck?
Benötigen Sie eine Laufzeitmechanik oder nur eine Kompilierzeitmechanik, um zwischen verschiedenen Implementierungen zu wechseln? Warum?
Welche Granularität benötigen Sie, um zwischen den Implementierungen zu wechseln? Alles auf einmal? Pro Modul (jeweils mit einer Gruppe von Klassen)? Pro Klasse?
Wer muss den Schalter steuern? Nur dein / dein Entwicklerteam? Ein Administrator? Jeder Kunde für sich? Oder die Wartungsentwickler für den Client-Code? Wie einfach / robust / kinderleicht muss die Mechanik sein?
Wählen Sie unter Berücksichtigung der Antworten auf diese Fragen die einfachste Lösung aus, die Ihnen die erforderliche Flexibilität bietet. Implementieren Sie keine Flexibilität, bei der Sie sich nicht sicher sind, ob dies zusätzlichen Aufwand bedeutet.
Als Antwort auf Ihre Antworten: Wenn Sie mindestens einen konkreten Anwendungsfall zur Hand haben, können Sie damit Ihre Entwurfsentscheidungen validieren. Verwenden Sie diesen Anwendungsfall, um herauszufinden, welche Art von Lösung für Sie am besten geeignet ist. Probieren Sie einfach aus, ob Sie von einer "Fabrik" oder einem "Service Locator" das bekommen, was Sie brauchen, oder ob Sie etwas anderes brauchen. Wenn Sie der Meinung sind, dass beide Lösungen für Ihren Fall gleich gut sind, werfen Sie einen Würfel.
quelle
Nur um sicherzugehen, dass ich das richtig hinbekomme. Sie haben einen Service, der sich aus einigen Klassen zusammensetzt,
C1,...,Cn
und eine Reihe von Kunden, die direkt anrufennew Ci(...)
. Daher stimme ich der allgemeinen Struktur Ihrer Lösung zu, die darin besteht, einen neuen internen Service mit einigen neuen Klassen zu erstellenD1,...,Dn
, die nett und modern sind und deren Abhängigkeiten einschleusen (indem solche Abhängigkeiten über den Stapel zugelassen werden) und anschließendCi
nichts anderes als instanziieren und neu schreiben delgate toDi
. Die Frage ist, wie es geht und Sie haben ein paar Möglichkeiten in2
und vorgeschlagen3
.Um einen ähnlichen Vorschlag zu machen
2
. Sie können die Abhängigkeitsinjektion durchgehendDi
und intern verwenden und dann einen normalen Kompositionsstamm erstellenR
(unter Verwendung eines Frameworks, wenn Sie dies für angemessen halten), der für die Zusammenstellung des Objektdiagramms verantwortlich ist. Schieben Sie sichR
hinter eine statische Fabrik und lassen Sie jedenCi
durchkommenDi
. Zum Beispiel könnten Sie habenIm Grunde ist dies Ihre Lösung 2, aber sie sammelt alle Ihre Fabriken an einem Ort zusammen mit dem Rest der Abhängigkeitsinjektion.
quelle
getInstance()
?Beginnen Sie mit einfachen, nichts Besonderes, singulären Implementierungen.
Wenn Sie später zusätzliche Implementierungen erstellen müssen, ist es eine Frage des Zeitpunkts, zu dem die Implementierungsentscheidung getroffen wird.
Wenn Sie Flexibilität bei der Installation benötigen (für jede Client-Installation wird eine einzelne statische Implementierung verwendet), müssen Sie trotzdem nichts Besonderes tun. Sie bieten nur verschiedene DLLs oder SOs an (oder was auch immer Ihr lib-Format ist). Der Kunde muss nur den richtigen in den
lib
Ordner legen ...Wenn Sie Laufzeitflexibilität benötigen, benötigen Sie lediglich einen Thin Adapter und einen Mechanismus zur Auswahl der Implementierung. Ob Sie eine Fabrik, einen Locator oder einen IoC-Container verwenden, ist größtenteils umstritten. Die einzigen signifikanten Unterschiede zwischen einem Adapter und einem Locator sind A) Benennung und B) Ob das zurückgegebene Objekt ein Singleton oder eine dedizierte Instanz ist. Und der große Unterschied zwischen einem IoC-Container und einer Fabrik / Lokalisierung ist , wer wen anruft . (Oft eine Frage der persönlichen Präferenz.)
quelle