Come passare i dati tra gli oggetti quando viene utilizzato un contenitore IoC?

4

Sto lavorando con un progetto che il nostro architetto ha deciso di utilizzare l'iniezione delle dipendenze per quasi tutto. Usiamo un contenitore IoC. Uno dei principali problemi che continuo a riscontrare quando utilizzo questo modello è "Come faccio a passare questo bit di dati qui a un oggetto diverso che verrà utilizzato in seguito?"

Spesso, la soluzione più semplice è quella di contrassegnare una classe specifica come un "singleton" con il nostro specifico iniettore / contenitore. Questo è male per alcune ragioni. Come tutti sappiamo, i singleton sono cattivi e non dovrebbero mai essere usati. A volte la classe contrassegnata come "singleton", in realtà dovrebbe avere più istanze quando viene utilizzata in diverse parti del codice. Contrassegnare una classe come "singleton" crea una nuova dipendenza che usiamo un injector / container che supporta il concetto di marcatura di una classe come "singleton".

Un'altra soluzione è la modifica del modo in cui l'iniettore / contenitore crea l'oggetto scrivendo chiusure o altra logica specifica quando l'iniettore crea un'istanza dell'oggetto in base a ciò che sta utilizzando il client e allo stato dei client. Questo è male perché crea una nuova dipendenza di questa funzionalità esistente nell'iniettore / contenitore. Inoltre viola la separazione delle preoccupazioni perché ora rende l'inject / container interessato a diverse classi diverse.

Ecco un esempio di caso di questo problema:

  1. Sto utilizzando ClassA nelle prime fasi dell'applicazione da ClassB. Ho alcuni stati / dati che voglio memorizzare in ClassA e usarli in seguito.
  2. Altre cose si verificano nell'applicazione. L'oggetto di ClassA è ancora nello stack più in basso, o quella parte della pila non esiste più e forse non ci sono riferimenti all'oggetto di ClassA. Il metodo di classB può essere o non essere ancora presente nello stack di chiamate.
  3. Più avanti nell'applicazione, voglio fare riferimento allo stato / dati di ClassA da ClassC.

Aggiornamento - Sto aggiungendo un esempio

class ClassA {
private:
    shared_ptr<ClassState> state;
    shared_ptr<ClassB> b;
public:
    ClassA(shared_ptr<ClassState> state, shared_ptr<ClassB> b) {
        this->state = state;
        this->b = b;
    }
    void run() {
        // do stuff
        if (true /* blah blah blah */) {
            state->setState(true);
        } else {
            state->setState(false);
        }
        // do more stuff
        b->run();
    }
};

class ClassB {
private:
    shared_ptr<ClassC> c;
public:
    ClassB(shared_ptr<ClassC> c) {
        this->c = c;
    }
    void run() {
        // do stuff
        c->run();
        // do more stuff
    }
};


class ClassC {
private:
    shared_ptr<ClassState> state;
public:
    ClassC(shared_ptr<ClassState> state) {
        this->state = state;
    }
    void run() {
        //This is where I want to use that data that was stored earlier
        // in the application
        if (state->getState()) {
            // do stuff
        }
    }
};

void main() {
    // Create whatever magicaly IOC dependency injector
    shared_ptr<ClassA> a = injector.create<ClassA> ();
    a->run();
}
    
posta Jacob Brown 01.11.2017 - 15:13
fonte

4 risposte

6

Sembra che tu voglia solo raggiungere e afferrare le cose non appena ne hai bisogno. Questo è tipico della programmazione procedurale. Nessuna lingua, struttura, modello o strumento può fermarti se insisti a lavorare in questo modo.

Tuttavia, c'è un altro modo. Racconta, non chiedere dice di non cercare di ottenere ciò di cui hai bisogno (come avviene in genere tramite getter e static riferimenti), ma per lasciare solo ciò che mai ti serve per essere consegnato a te. In questo modo non devi sapere come trovarlo. Ti trova.

Quindi hai ClassC che ha bisogno di qualcosa da ClassA . Piuttosto che scrivere ClassB.ClassA.getNeededThing() scrivi

class ClassC {
    String neededThing;
    public ClassC(String neededThing) { 
        this.neededThing = neededThing;
    }         
} 

In questo modo ClassC non esiste nemmeno finché non ha ciò che deve essere utile. E non importa se fosse ClassA o ClassB a capire come ottenerlo di cosa ha bisogno.

Questa è solo la creazione di oggetti. Dopo che gli oggetti sono stati creati possono ancora parlarsi se hanno riferimenti l'uno all'altro. Diciamo che c'era anotherNeededThing di cui ClassC ha bisogno da ClassA .

class ClassA {
    ClassC c;
    String someOtherNeededThing;
    public ClassA(ClassC c, String someOtherNeededThing){ 
        this.c = c;
        this.someOtherNeededThing = someOtherNeededThing;
    }
    public void timeToGiveTheThing() {
        c.doYourThingWith(someOtherNeededThing);
    }      
} 

Fatto in questo modo ClassC non ha nemmeno bisogno di sapere che ClassA esiste.

Potresti pensare di aver bisogno di un contenitore DI di fantasia per far funzionare tutto questo ma può essere fatto in main (se ne hai uno, se non trovi la tua "composizione root" e fallo lì).

int main() {
    ClassC c = new ClassC("Hello ");
    ClassA a = new ClassA("world", c);
    a.timeToGiveTheThing();
}

Ma sei bloccato con un contenitore DI che dici? Bene, basta elencare C come una delle dipendenze di A. Finché ottieni un riferimento, puoi parlarci. Fintanto che qualsiasi cosa sa quando ciò dovrebbe accadere ha un riferimento a te, quindi parlerai con lui quando dovresti. Non c'è bisogno di chiedere.

Se vieni dalla programmazione procedurale, ti sentirai molto diverso dal tuo solito modo di programmazione. È un modo diverso Chiamiamo questa programmazione orientata agli oggetti di stile. Se pensavi di averlo fatto prima perché stavi usando un linguaggio OOP mi dispiace, questo non garantisce che tu stia usando OOP.

Una volta capito, probabilmente ti troverai a creare costruttori e firme di metodi con tonnellate di argomenti. Questo è il momento di imparare l'ossessione primitiva e gli oggetti parametrici. Basta non confondere gli oggetti parametro con oggetti incapsulati.

    
risposta data 01.11.2017 - 15:51
fonte
1

2.Some more stuff happens in the application. The object of ClassA is either still on the stack farther down, or that part of the stack no longer exists and maybe there are no references to the object of ClassA. The method of classB may or may not still be on the call stack.

Se questo è il caso, prima che la Classe A scompaia, ha bisogno di persistere i suoi dati per memorizzare qualche tipo in modo che la classe C possa accedere a quei dati.

Se si utilizza l'iniezione di dipendenza per iniettare un'istanza di Classe A in Classe C perché sono necessari i suoi dati, sarà necessario scegliere il tipo di oggetto di cui avrà bisogno la classe A di durata utile; per esempio, single, ecc.

Sembra che il contenitore DI potrebbe dover creare una singola istanza di quella classe se è necessario aver bisogno di mantenere tale classe in vita. I contenitori DI dispongono di opzioni per la gestione della durata dell'oggetto o semplicemente dei dati persistenti e ricevono la classe C per ottenere i dati e rimuovere la dipendenza dalla classe A.

    
risposta data 01.11.2017 - 16:28
fonte
1

Sto attraversando un periodo difficile, comprendendo il tuo problema, soprattutto comprendendo in che modo vedi il tuo problema relativo a dependency injection (DI). Il tuo problema appare da una vista di 30000 piedi più come un problema architettonico / organizzativo all'interno della tua base di codice, che un problema di DI. Senza DI i tuoi oggetti devono comunque risolvere questo problema.

Ci sono diversi costrutti per aiutare gli oggetti comunicare l'un l'altro.

La più semplice utilizza il schema di osservazione . Dove un oggetto è la fonte ( Subject ) di informazioni e uno o più oggetti sottoscrivi ( Observer ) a messaggi emesso dalla fonte . Ogni volta che la fonte emette un evento, gli osservatori sono notfied . Questo funziona per la vita di entrambi: Oggetto e Osservatore . E poiché entrambi sono collegati tramite riferimento, lo è anche la loro durata.

Se vuoi disaccoppiare di più devi usare qualche fonte di verità esterna :

es.

  • Le informazioni vengono mantenute in forma di file nel filesystem
  • Le informazioni vengono mantenute come dati nel database
  • Le informazioni vengono mantenute su un processo in esecuzione (nel database di memoria, Genserver , Coda messaggi, ecc.)

La tua Subject diventa una Producer e la Observer diventa una Consumer e la comunicazione diventa asincrona .

"How do I pass this bit of data here to a different object that will be used later?"

Comunicare la modifica sincrona via Modello osservatore o asincrona .

Often, the easiest solution is to mark a specific class as being a "singleton" with our specific injector/container.

Da quello che scrivi, presumo, ti occupi di DI in un contesto di applicazioni web. Non c'è alcun problema nell'usare Singleton dire in una Spring-Application. Singleton significa in questo contesto, che l'oggetto viene creato una volta e non per ogni richiesta (cfr. Guida rapida agli ambiti dei bean primaverili ). Se assicuri l'accesso gratuito alle condizioni di gara, non c'è nulla da dire contro.

D'altra parte prototype -I fagioli non animati vivono solo per il tempo che viene consumato e vengono generati ogni volta che viene richiesto. Questi potrebbero essere stateful, ma il loro stato cessa con la loro vita.

Another solution is the modify how the injector/container creates the object by writing closures, or other specific logic when the injector is instantiating the object based on what is client is using it and the state of the clients.

Questo non è molto diverso dall'uso di un singleton.

Here is an example use case of this problem:

Come detto sopra, devi stabilire una comunicazione tra i tuoi oggetti.

    
risposta data 01.11.2017 - 17:23
fonte
1

I singleton sono scoraggiati quando sono associati a un riferimento statico globale perché nascondono le dipendenze tra le classi. Singletons nel contesto di un contenitore DI sono in realtà una grande idea.

  1. Crea una classe singleton responsabile della gestione di quei dati particolari in un modo sicuro per i thread
  2. Crea una classe astratta come interfaccia pubblica per quel singleton che fornisce accesso in lettura / scrittura ai dati
  3. Utilizza l'iniezione del costruttore per condividere i dati tra gli oggetti che ne hanno bisogno utilizzando l'interfaccia pubblica.

Semplice, sicuro e facile da capire.

    
risposta data 02.11.2017 - 16:49
fonte

Leggi altre domande sui tag