Moltitudini che costruiscono un'implementazione. DI senza speranza? Utilizzare il localizzatore di servizi?

13

Diciamo che abbiamo 1001 clienti che costruiscono direttamente le loro dipendenze piuttosto che accettare iniezioni. Refactoring del 1001 non è un'opzione secondo il nostro capo. In realtà non è nemmeno consentito l'accesso alla loro fonte, solo i file di classe.

Quello che dovremmo fare è "modernizzare" il sistema che questi 1001 clienti attraversano. Possiamo refactoring che tutto ciò che ci piace. Le dipendenze fanno parte di quel sistema. E alcune di quelle dipendenze dovremmo cambiare per avere una nuova implementazione.

Quello che vorremmo fare è avere la possibilità di configurare diverse implementazioni di dipendenze per soddisfare questa moltitudine di client. Purtroppo, DI non sembra un'opzione in quanto i clienti non accettano iniezioni con costruttori o setter.

Le opzioni:

1) Rifattora l'implementazione del servizio che i client usano in modo che faccia ciò di cui i clienti hanno bisogno ora. Bang, abbiamo finito Non flessibile. Non complesso.

2) Rifattora l'implementazione in modo che deleghi il suo lavoro a un'altra dipendenza che acquisisce attraverso una fabbrica. Ora possiamo controllare quale implementazione usano tutti rifattando la fabbrica.

3) Rifattora l'implementazione in modo che deleghi il suo lavoro a un'altra dipendenza che acquisisce tramite un localizzatore di servizi. Ora possiamo controllare quale implementazione usano tutti configurando il localizzatore di servizio che potrebbe essere semplicemente un hashmap di stringhe sugli oggetti con un piccolo casting in corso.

4) Qualcosa a cui non ho ancora pensato.

L'obiettivo:

Riduci al minimo il danno al design causato dal trascinamento del vecchio codice client mal progettato in futuro senza aggiungere complessità inutile.

I clienti non dovrebbero conoscere o controllare l'implementazione delle loro dipendenze ma insistono nel costruirli con new . Non possiamo controllare new ma controlliamo la classe che stanno costruendo.

La mia domanda:

Che cosa ho omesso di considerare?

Domande da Doc Brown

do you really need a possibility to configure between different implementations? For what purpose?

Agilità. Molte incognite La direzione vuole il potenziale per il cambiamento. Solo perdere la dipendenza dal mondo esterno. Anche test.

do you need need a run time mechanics, or just a compile time mechanics to switch between different implementations? Why?

È abbastanza probabile compilare la meccanica del tempo. Tranne test.

which granularity do you need to switch between implementations? All at once? Per module (each containing a group of classes)? Per class?

Del 1001 solo uno viene eseguito attraverso il sistema in qualsiasi momento. Cambiare ciò che tutti i client usano contemporaneamente è probabile che vada bene. Il controllo individuale delle dipendenze è probabilmente importante però.

who needs to control the switch? Only your/your developer team? An administrator? Each client on his own? Or the maintenance developers for the client's code? So how easy/robust/foolproof does the mechanics need to be?

Dev per test. Amministratore come cambiano le dipendenze dell'hardware esterno. Deve essere facile da testare e configurare.

Il nostro obiettivo è mostrare che il sistema può essere rifatto rapidamente e modernizzato.

actual use case for the implementation switch?

Uno è, alcuni dati saranno forniti dal software fino a quando la soluzione hardware non sarà pronta.

    
posta candied_orange 02.02.2017 - 04:27
fonte

3 risposte

6

Beh, non sono sicuro di aver compreso completamente i dettagli tecnici e le loro esatte differenze delle tue soluzioni supportate, ma IMHO devi prima scoprire quale tipo di flessibilità hai davvero bisogno.

Le domande che devi porsi sono:

  • hai davvero bisogno di una possibilità di configurazione tra diverse implementazioni? Per quale scopo?

  • hai bisogno di una meccanica del tempo di esecuzione, o solo una meccanica del tempo di compilazione per passare da diverse implementazioni? Perché?

  • quale granularità hai bisogno di passare da una implementazione all'altra? Tutto in una volta? Per modulo (ciascuno contenente un gruppo di classi)? Per classe?

  • chi deve controllare l'interruttore? Solo il tuo / il tuo team di sviluppatori? Un amministratore? Ogni cliente da solo? O gli sviluppatori di manutenzione per il codice del cliente? Quindi, quanto è semplice / robusta / infallibile la meccanica necessaria?

Avendo in mente le risposte a queste domande, scegli la soluzione più semplice che ti viene in mente e che ti fornisce la flessibilità necessaria. Non implementare alcuna flessibilità di cui non sei sicuro riguardo "nel caso in cui" se ciò comporta uno sforzo aggiuntivo.

Come risposta alle tue risposte: se hai almeno un caso di utilizzo reale a portata di mano, utilizzalo per convalidare le tue decisioni di progettazione. Usa questo caso d'uso per scoprire quale tipo di soluzione funziona meglio per te. Prova solo se una "fabbrica" o un "localizzatore di servizi" ti forniscono ciò di cui hai bisogno o se hai bisogno di qualcos'altro. Se pensi che entrambe le soluzioni siano ugualmente buone per il tuo caso, lancia un dado.

    
risposta data 02.02.2017 - 07:05
fonte
2

Solo per assicurarmi che stia andando bene. Hai qualche servizio fatto di alcune classi, diciamo C1,...,Cn e un gruppo di client che chiamano direttamente new Ci(...) . Quindi penso che sono d'accordo con la struttura generale della soluzione che è quello di creare un nuovo servizio interno con alcune nuove classi D1,...,Dn che sono bello e moderno e iniettano loro dipendenze (che consentono una simile dipendenze attraverso la pila) e poi riscrivere Ci non fare altro che istanziare e delgate su Di . La domanda è come farlo e hai suggerito un paio di modi in 2 e 3 .

Per dare un suggerimento simile a 2 . È possibile utilizzare l'iniezione di dipendenza in Di e internamente e quindi creare una radice di composizione normale R (utilizzando un framework se lo si ritiene appropriato) responsabile dell'assemblaggio del grafico dell'oggetto. Sposta R dietro un factory statico e consenti a ogni Ci di ottenere il Di . Quindi ad esempio potresti avere

public class OldClassI {
    private final NewClassI newImplementation;

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

Essenzialmente questa è la soluzione 2, ma raccoglie tutte le tue fabbriche in un'unica posizione insieme al resto dell'iniezione delle dipendenze.

    
risposta data 02.02.2017 - 06:26
fonte
1

Inizia con semplici, fantasiose e singolari implementazioni.

Se successivamente hai bisogno di creare implementazioni aggiuntive, è una questione di quando si verifica la decisione di implementazione.

Se hai bisogno di flessibilità "install-time" (l'installazione di ogni client utilizza un'unica implementazione statica), non hai ancora bisogno di fare niente di speciale. Tu offri solo DLL o SO diverse (o qualunque sia il tuo formato lib). Il client deve solo posizionare quello giusto nella cartella lib ...

Se hai bisogno di flessibilità di runtime, ti servirà solo un adattatore sottile e un meccanismo di selezione delle implementazioni. Se si utilizza un factory, un localizzatore o un container IoC è per lo più discutibile. Le uniche differenze significative tra un adattatore e un localizzatore sono A) Naming e B) Se l'oggetto restituito è un singleton o un'istanza dedicata. E la grande differenza tra un contenitore IoC e un factory / locator è chi chiama chi . (Spesso una questione di preferenze personali.)

    
risposta data 02.02.2017 - 17:12
fonte