Esistono alternative all'iniezione delle dipendenze per le classi stateless?

7

Sto lavorando su un'applicazione in cui ho progettato classi per adattarsi a diversi gruppi:

  • Immutabile: inizializzato tramite costruttori, utilizza l'idioma copy-and-swap (inc. move), può copiare in profondità (cioè clone), ha solo "getter" (non "setter") e implementa operatori di confronto (==,! =);
  • Servizi: classi stateless che hanno metodi che accettano immutabili e / o servizi per eseguire le funzioni desiderate;
  • Costruttori: fabbriche ecc. per costruire immutables.

Il test delle unità sui miei immutables è stato semplice. Posso usare l'iniezione di dipendenza attraverso i costruttori. Ciò significa che posso scambiare le classi di test per assicurarmi di essere un test unitario (al contrario dei test di integrazione). Posso usare i costruttori per costruire i miei oggetti di produzione. Questo elemento del mio design mi soddisfa in termini di test e produzione.

Tuttavia, sui miei servizi mi sembra di essere in grado di mantenere queste classi stateless e testabili dell'unità usando l'integrazione delle dipendenze tramite gli argomenti del metodo.

Per i miei servizi, una funzione di esempio cambia da:

virtual unsigned long foo() const override final;

... a:

virtual unsigned long foo(const ISomeInterface & dependency) const override final;

Questo significa che le mie classi di servizio sono testabili, ma ora devo istanziare le dipendenze al di fuori della classe quando si utilizza il codice in produzione. Ad esempio:

// without dependency injection
Service service;
return service.foo();

// with dependency injection
Service service;
Dependency dependency;
return service.foo(dependency);

Questo ha portato a un gran numero di mie classi di servizio che ora richiedono almeno 1 argomento in più per ogni metodo di classe. Nota: questo è l'approccio che sto attualmente utilizzando nel mio codice.

La mia domanda è questa:

Quali alternative ho a questa forma di iniezione delle dipendenze che mi permette di:

  • unit test delle classi stateless (senza le dipendenze)
  • mantieni queste classi senza stato
  • riduci / nascondi il codice che crea un'istanza delle dipendenze (in particolare se un oggetto ha più dipendenze)

Nota - Sto anche eseguendo test di integrazione, che testano le reali dipendenze tra gli oggetti.

    
posta Class Skeleton 27.05.2016 - 12:57
fonte

4 risposte

4

Se non ti piacciono gli argomenti aggiuntivi del costruttore per le dipendenze hai bisogno di un DI-Container per gestire la creazione dell'istanza per te.

Puoi utilizzare un quadro di-container esistente o implementare una versione povera da solo

public PoorMansDiContainer : IPoorMansDiContainer {
    private IService mService = null;
    private IFooService mFooService = null;

    public IService getSingletonService() {
        if (mService == null) {
            mService = new ServiceImpl(getSingletonFooService());
        }
        return mService;
    }
    public IFooService getSingletonFooService() {
        if (mFooService == null) {
            mFooService = new FooServiceImpl();
        }
        return mFooService;
    }
}
    
risposta data 27.05.2016 - 14:44
fonte
1

Potresti configurare le tue classi stateless per fare riferimento direttamente alle loro dipendenze usando i template. Ad esempio:

// a dependency interface...
class ILogger
{
public:
    virtual void logSomething (std::string message);
};

// an interface for a service
class IMyService
{
public:
    virtual std::unique_ptr<MyClass> getMyObject () = 0;
};

template <LogProvider>
class MyServiceImpl : public IMyService
{
public:
     virtual std::unique_ptr<MyClass> getMyObject ()
     {
          return LogProvider::logger->logSomething ("creating my object");
          return std::make_unique<MyClass> ();
     }
};

// at global level
struct GetLogger 
{
    static std::shared_ptr<ILogger> logger;
}

// initialisation code...
GetLogger::logger = // ... make a concrete logger here
std::unique_ptr<IMyServce> myService = 
   std::make_unique<MyServiceImpl<GetLogger>> ();

È quindi possibile creare istanze di MyServiceImpl utilizzando qualsiasi classe desiderata che contenga un logger. Questo approccio crea dati globali, ma poiché lo utilizza solo indirettamente, non vedo un problema con esso .

Anche se, detto questo, personalmente abbandonerei la nozione di classi di servizio stateless e passerei invece a classi di servizio immutabili, poiché rappresenta una soluzione molto più semplice.

    
risposta data 29.05.2016 - 16:23
fonte
0

Utilizza un potente localizzatore di servizi, che è, come ti dirà Martin Fowler, praticamente equivalente a DI. Quindi imposta il servizio su qualsiasi simulazione ti piace per la durata del test nel contesto in cui il test è in esecuzione. per esempio. (Codice C #, scusa)

public class Service()
{
  public void Foo()
  {
    ...
    IDependency dependency = ServiceLocator.Get<IDependency>();
    ...
  }
}

Per il test

ServiceLocator.SetForThisThread<IDependency>(typeof(MockDependency));
RunTest();

Se qualcuno volesse lamentarsi del fatto che si sta creando una dipendenza da ServiceLocator, mi piacerebbe davvero capire come potrebbe mai essere un problema.

    
risposta data 28.05.2016 - 13:07
fonte
0

Sono riuscito a ripulire le mie classi stateless usando gli argomenti predefiniti. Ad esempio,

virtual unsigned long foo(
    const ISomeInterface & dependency = ProductionDependency()) const override final;

Sembra che abbia soddisfatto tutti i criteri nella mia domanda originale ...

classi test delle classi stateless (senza le dipendenze)

Posso testare l'unità specificando l'argomento (ad esempio, passare un oggetto di prova falso).

// unit test snippet
Service service;
TestDependency dependency;
const auto actual = service.foo(dependency);

mantieni queste classi senza stato

Le classi senza stato usano ancora solo i costruttori predefiniti e non hanno membri di dati.

riduce / nasconde il codice che crea un'istanza delle dipendenze (in particolare se un oggetto ha più dipendenze)

Avevo bisogno di apportare le seguenti modifiche:

  • Le intestazioni delle classi di base del servizio ora includono le intestazioni di dipendenza di produzione
  • Gli argomenti del metodo ora includono argomenti predefiniti per ogni dipendenza
  • In alcuni casi ho dovuto modificare l'ordine degli argomenti per garantire che gli argomenti predefiniti fossero gli argomenti finali

Tuttavia, ora non ho più bisogno di includere queste dipendenze nel mio codice cliente. Ad esempio,

// client code snippet
Service service;
return service.foo();

Questo è lo stesso anche se la mia classe di servizio ha più dipendenze. Finché utilizzo gli argomenti predefiniti, posso usare lo stesso snippet come sopra. I client non hanno più bisogno di conoscere le dipendenze (o, più specificamente, hanno bisogno di istanziare gli oggetti di dipendenza perché vengono liberati tramite argomenti predefiniti).

    
risposta data 15.06.2016 - 15:49
fonte

Leggi altre domande sui tag