Moltitudini che costruiscono un'implementazione. DI senza speranza? Utilizzare il servizio di localizzazione?


14

Supponiamo di avere 1001 clienti che costruiscono le loro dipendenze direttamente anziché accettare iniezioni. Il refactoring del 1001 non è un'opzione secondo il nostro capo. In realtà non ci è nemmeno permesso l'accesso alla loro fonte, solo ai file di classe.

Ciò che dovremmo fare è "modernizzare" il sistema che attraversano questi 1001 client. Possiamo riformattare tutto ciò che ci piace. Le dipendenze fanno parte di quel sistema. E alcune di quelle dipendenze che 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 clienti. Purtroppo, DI non sembra un'opzione poiché i clienti non accettano iniezioni con costruttori o setter.

Le opzioni:

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

2) Rifattorizzare 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 refactoring della fabbrica.

3) Rifattorizzare l'implementazione in modo da delegare il lavoro a un'altra dipendenza acquisita tramite un localizzatore di servizi. Ora possiamo controllare quale implementazione usano tutti configurando il localizzatore di servizi che potrebbe essere semplicemente una hashmapstringa di oggetti con un piccolo casting in corso.

4) Qualcosa a cui non ho ancora pensato.

L'obiettivo:

Ridurre al minimo i danni di progettazione causati trascinando il vecchio codice client mal progettato in futuro senza aggiungere inutili complessità.

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

La mia domanda:

Cosa non ho considerato?


Domande di Doc Brown

hai davvero bisogno della possibilità di configurare tra diverse implementazioni? Per quale scopo?

Agilità. Molte incognite. La direzione desidera un potenziale cambiamento. Perdere dipendenza solo dal mondo esterno. Anche test.

hai bisogno di una meccanica del tempo di esecuzione o solo di una meccanica del tempo di compilazione per passare da una implementazione all'altra? Perché?

La meccanica del tempo di compilazione è probabilmente sufficiente. Tranne che per i test.

quale granularità è necessario per passare da una implementazione all'altra? Tutto in una volta? Per modulo (ognuno contenente un gruppo di classi)? Per classe?

Dei 1001 solo uno viene eseguito nel sistema alla volta. Cambiare ciò che tutti i client utilizzano contemporaneamente è probabilmente corretto. Il controllo individuale delle dipendenze è tuttavia probabilmente importante.

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 deve essere semplice / robusta / infallibile la meccanica?

Dev per i test. L'amministratore mentre le dipendenze hardware esterne cambiano. Deve essere facile da testare e configurare.


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


caso d'uso effettivo per lo switch di implementazione?

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


1
Non credo che otterrai grandi risultati immagazzinando fabbriche o localizzatori nello stato globale. Perché preoccuparsi? Stai per testare i clienti sostituendo la dipendenza?
Basilevs,

Prendi in considerazione l'utilizzo del caricatore di classi personalizzato.
Basilevs,

Solo per curiosità, cosa fa questo sistema?
Prime il

Risposte:


7

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

Le domande che devi porti sono:

  • hai davvero bisogno della possibilità di configurare tra diverse implementazioni? Per quale scopo?

  • hai bisogno di una meccanica del tempo di esecuzione o solo di una meccanica del tempo di compilazione per passare da una implementazione all'altra? Perché?

  • quale granularità è necessario per passare da una implementazione all'altra? Tutto in una volta? Per modulo (ognuno 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 deve essere semplice / robusta / infallibile la meccanica?

Avendo in mente le risposte a queste domande, scegli la soluzione più semplice che ti viene in mente e che ti offre la flessibilità richiesta. Non implementare alcuna flessibilità di cui non si è sicuri "per ogni evenienza" se ciò implica uno sforzo aggiuntivo.

Come risposta alle vostre risposte: se si dispone di almeno un vero e proprio caso d'uso a portata di mano, usarlo per convalidare le decisioni di progettazione. Usa quel 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.


Buone domande! Si prega di consultare l'aggiornamento.
candied_orange,

Aggiornato con il caso d'uso reale.
candied_orange,

@CandiedOrange: non posso darti del pesce, posso solo provare ad aiutarti a pescare da solo :-)
Doc Brown,

Inteso. Sto solo fissando l'acqua chiedendomi se ho bisogno di un'esca migliore o di una diversa canna da pesca. Mi piacerebbe fare un DI corretto ma questa situazione non sembra permetterlo.
candied_orange,

1
@CandiedOrange Non lasciarti andare al desiderio di fare DI perché è "buono" o "migliore" di ogni altra cosa. DI è una soluzione particolare a un problema (o talvolta diversi). Ma non è sempre la soluzione più "adatta". Non innamorartene ... trattalo come è. È uno strumento.
svidgen,

2

Solo per essere sicuro che sto andando bene. Hai un servizio fatto di alcune classi, diciamo C1,...,Cne un gruppo di clienti che chiamano direttamente new Ci(...). Quindi penso di essere d'accordo con la struttura generale della tua soluzione che è quella di creare un nuovo servizio interno con alcune nuove classi D1,...,Dnche sono belle e moderne e iniettano le loro dipendenze (permettendo tali dipendenze attraverso lo stack) e quindi riscrivendole Ciper fare altro che istanziare e delgate a Di. La domanda è come farlo e hai suggerito un paio di modi in 2e 3.

Per dare un suggerimento simile a 2. È possibile utilizzare l'iniezione delle dipendenze Diall'interno e internamente e quindi creare una normale radice di composizione R(utilizzando un framework se lo si ritiene appropriato) che è responsabile dell'assemblaggio del grafico degli oggetti. Spara Rdietro una fabbrica statica e lascia che ognuno Ciriesca a Disuperarla. Quindi, per esempio, potresti averlo

public class OldClassI {
    private final NewClassI newImplementation;

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

Fondamentalmente questa è la tua soluzione 2, ma raccoglie tutte le tue fabbriche in un'unica posizione insieme al resto dell'iniezione di dipendenza.


Sono d'accordo con questo. Non sei sicuro di come questo non sia lo stesso del localizzatore di servizi dall'opzione 3. Ti aspetti di creare una nuova istanza con ogni chiamata a getInstance()?
candied_orange,

@CandiedOrange Probabilmente questo è solo uno scontro nell'uso, per me un localizzatore di servizi è molto specificamente qualcosa che è essenzialmente solo una hashmap dai tipi agli oggetti, mentre questo la radice della composizione è solo un oggetto con un mucchio di metodi che costruiscono altri oggetti.
walpen

1

Inizia con implementazioni semplici, niente di speciale, singolari.

Se in seguito è necessario creare implementazioni aggiuntive, si tratta di quando si verifica la decisione di implementazione.

Se hai bisogno di flessibilità "al momento dell'installazione" (ogni installazione client utilizza un'unica implementazione statica), non hai ancora bisogno di fare nulla di speciale. Offrite solo diverse DLL o SO (o qualunque sia il vostro formato lib). Il client deve solo mettere quello giusto nella libcartella ...

Se hai bisogno di flessibilità di runtime, avrai solo bisogno di un adattatore sottile e un meccanismo di selezione dell'implementazione. Sia che usi una fabbrica, un localizzatore o un contenitore IoC è per lo più discutibile. Le uniche differenze significative tra un adattatore e un localizzatore sono A) Denominazione e B) Se l'oggetto restituito è un singleton o un'istanza dedicata. E la grande differenza tra un contenitore IoC e una fabbrica / localizzatore è chi chiama chi . (Spesso una questione di preferenze personali.)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.