Eine Vielzahl von Implementierungen. DI hoffnungslos? Service Locator verwenden?


14

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 hashmapaus 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, newaber wir kontrollieren die Klasse, die sie bauen.

Meine Frage:

Was habe ich nicht berücksichtigt?


Fragen von Doc Brown

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.


1
Ich glaube nicht, dass Sie viel erreichen werden, wenn Sie Fabriken oder Standorte in einem globalen Zustand lagern. Warum die Mühe? Werden Sie Clients testen, die die Abhängigkeit ersetzen?
Basilevs

Erwägen Sie die Verwendung eines benutzerdefinierten Klassenladeprogramms.
Basilevs

Was kann dieses System aus Neugier?
Prime

Antworten:


7

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.


Gute Fragen! Bitte siehe Update.
candied_orange

Aktualisiert mit aktuellem Anwendungsfall.
candied_orange

@CandiedOrange: Ich kann dir keinen Fisch geben, ich kann nur versuchen, dir selbst beim Angeln zu helfen :-)
Doc Brown

Verstanden. Ich starre nur auf das Wasser und frage mich, ob ich einen besseren Köder oder ein anderes Angelloch brauche. Ich würde gerne eine richtige DI machen, aber diese Situation scheint es nicht zu erlauben.
candied_orange

1
@CandiedOrange Lassen Sie sich nicht auf das Verlangen ein, DI zu machen, weil es "gut" oder "besser" ist als alles andere. DI ist eine bestimmte Lösung für ein Problem (oder manchmal mehrere). Aber es ist nicht immer die "passendste" Lösung. Sei nicht in ihn verliebt ... behandle ihn so wie er ist. Es ist ein Werkzeug.
Svidgen

2

Nur um sicherzugehen, dass ich das richtig hinbekomme. Sie haben einen Service, der sich aus einigen Klassen zusammensetzt, C1,...,Cnund eine Reihe von Kunden, die direkt anrufen new Ci(...). Daher stimme ich der allgemeinen Struktur Ihrer Lösung zu, die darin besteht, einen neuen internen Service mit einigen neuen Klassen zu erstellen D1,...,Dn, die nett und modern sind und deren Abhängigkeiten einschleusen (indem solche Abhängigkeiten über den Stapel zugelassen werden) und anschließend Cinichts anderes als instanziieren und neu schreiben delgate to Di. Die Frage ist, wie es geht und Sie haben ein paar Möglichkeiten in 2und vorgeschlagen 3.

Um einen ähnlichen Vorschlag zu machen 2. Sie können die Abhängigkeitsinjektion durchgehend Diund intern verwenden und dann einen normalen Kompositionsstamm erstellen R(unter Verwendung eines Frameworks, wenn Sie dies für angemessen halten), der für die Zusammenstellung des Objektdiagramms verantwortlich ist. Schieben Sie sich Rhinter eine statische Fabrik und lassen Sie jeden Cidurchkommen Di. Zum Beispiel könnten Sie haben

public class OldClassI {
    private final NewClassI newImplementation;

    public OldClassI(Object parameter) {
        this.newImplementation = CompositionRoot.getInstance().getNewClassI(parameter);
    }
}

Im Grunde ist dies Ihre Lösung 2, aber sie sammelt alle Ihre Fabriken an einem Ort zusammen mit dem Rest der Abhängigkeitsinjektion.


Ich stimme dem zu. Sie sind sich jedoch nicht sicher, ob dies nicht mit dem Service-Locator aus Option 3 identisch ist. Erwarten Sie, bei jedem Aufruf von eine neue Instanz zu erstellen getInstance()?
candied_orange

@CandiedOrange Dies ist wahrscheinlich nur ein Konflikt in der Verwendung. Für mich ist ein Service-Locator sehr spezifisch und im Wesentlichen nur eine Hashmap von Typen zu Objekten, wohingegen der Kompositionsstamm nur ein Objekt mit einer Reihe von Methoden ist, die andere Objekte konstruieren.
Walpen

1

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 libOrdner 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.)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.