Service-locator anti-pattern alternativo

8

Sto usando Unity come IoC con C #, ma suppongo che la domanda non sia realmente limitata a Unity e C #, ma IoC in generale.

Cerco di seguire il principio SOLID, il che significa che ho ottenuto pochissime dipendenze tra due classi concrete. Ma quando ho bisogno di creare nuove istanze di un modello, qual è il modo migliore per farlo?

Di solito uso una factory per creare le mie istanze, ma ci sono alcune alternative e mi chiedo quale sia il migliore, e perché?

Fabbrica semplice:

public class FooFactory : IFooFactory
{
    public IFoo CreateModel()
    {
        return new Foo(); // references a concrete class.
    }
}

Servizio-locator-fabbrica

public class FooFactory : IFooFactory
{
    private readonly IUnityContainer _container;

    public FooFactory (IUnityContainer container)
    {
        _container = container;
    }

    public IFoo CreateModel()
    {
        return _container.Resolve<IFoo>(); // Service-locator anti-pattern?
    }
}

Func-fabbrica. Nessuna dipendenza dalle altre classi.

public class FooFactory : IFooFactory
{
    private readonly Func<IFoo> _createFunc;

    public FooFactory (Func<IFoo> createFunc)
    {
        _createFunc= createFunc;
    }

    public IFoo CreateModel()
    {
        return _createFunc(); // Is this really better than service-locator?
    }
}

Quale% di co_de dovrei usare e perché? Esiste un'opzione migliore?

Gli esempi sopra riportati sono di livello più concettuale, in cui cerco di trovare un equilibrio tra SOLID, codice gestibile e localizzatore di servizi. Ecco un esempio reale:

public class ActionScopeFactory : IActionScopeFactory
{
    private readonly Func<Action, IActionScope> _createFunc;

    public ActionScopeFactory(Func<Action, IActionScope> createFunc)
    {
        _createFunc = createFunc;
    }

    public IActionScope CreateScope(Action action)
    {
        return _createFunc(action);
    }
}

public class ActionScope : IActionScope, IDisposable
{
    private readonly Action _action;

    public ActionScope(Action action)
    {
        _action = action;
    }

    public void Dispose()
    {
        _action();
    }
}

public class SomeManager
{
    public void DoStuff()
    {
        using(_actionFactory.CreateScope(() => AllDone())
        {
           // Do stuff. And when done call AllDone().
           // Another way of actually writing try/finally.
        }
    }
}

Perché uso una fabbrica? Perché a volte ho bisogno di creare nuovi modelli. Ci sono vari scenari quando questo è necessario. Per es. in un mappatore e quando il mappatore ha una durata maggiore rispetto all'oggetto che dovrebbe mappare. Esempio di utilizzo di fabbrica:

public class FooManager
{
    private IService _service;
    private IFooFactory _factory;

    public FooManager(IService service, IFooFactory factory)  
    {
        _service = service;
        _factory = factory;
    }

    public void MarkTimestamp()
    {
        IFoo foo = _factory.CreateModel();
        foo.Time = DateTime.Now;
        foo.User = // current user
        _service.DoStuff(foo);
    }

    public void DoStuffInScope()
    {
        using(var foo = _factoru.CreateModel())
        {
            // do stuff with foo...
        }
    }
}
    
posta smoksnes 29.04.2016 - 15:38
fonte

2 risposte

8

Per prima cosa, il mantra secondo cui i localizzatori di servizi sono un anti-modello è faticoso e controproducente. Hanno i loro lati negativi, ma sono praticamente uguali ai contenitori IoC convenzionali ad eccezione dei contenitori IoC buoni .

Detto questo, concentriamoci sui tuoi esempi:

Le semplici fabbriche sono buone. Sono chiari Sono facili da testare. Sono facili da estendere.

Le fabbriche di localizzatori di servizi sono eccessive per questo scenario (e la maggior parte degli scenari, francamente). I localizzatori di servizio ti consentono di avere una fabbrica che può risolvere da qualsiasi tipo arbitrario a un'istanza di quel tipo (o un'eccezione). Non ne hai bisogno qui. Devi solo essere in grado di fornire un po 'di IFoo .

Il func-factory è strano qui. Non hai davvero bisogno dell'interfaccia, dal momento che Func<IFoo> è l'interfaccia (nella definizione "un punto in cui due cose interagiscono"). A volte hai già l'interfaccia e la fabbrica delle funzioni non è una fabbrica, ma un adattatore tra l'interfaccia I e la "interfaccia".

Quale migliore può variare in base alle tue esigenze. Personalmente, mi piace avere un Func . Offre la massima flessibilità d'uso, pur non avendo l'overhead della piastra di montaggio della fabbrica semplice.

E per quanto difendo i localizzatori di servizi non essendo un anti-pattern, hanno un caso di utilizzo limitato molto , e i tuoi esempi non si avvicinano nemmeno ad esso.

    
risposta data 29.04.2016 - 16:46
fonte
0

Le fabbriche sono un'eccezione per:

  • no localizzatore di servizi
  • no singleton
  • nessuna variabile globale
  • nessuna dipendenza strettamente accoppiata

regole.

In teoria, un codice aziendale sarebbe diviso in due parti principali:

  1. Costruttori di diagrammi di oggetti : posiziona dove le classi sono new ed diventano oggetti e vengono combinati insieme in base a configurazione specifica, flag o conoscenza generale
  2. livello aziendale : posto che contiene praticamente tutto ciò che non appartiene al gruppo 1, è qui che scrivi le regole aziendali, dove tu (se vuoi seguire i principi del codice pulito) segui IoC (DI per esempio) e dove le classi dovrebbero avere un basso accoppiamento, alta coesione e interfacce concise

Ma anche allora, anche se avere un localizzatore di servizi all'interno delle fabbriche non è esattamente un grosso problema, ci sono situazioni in cui una fabbrica scritta manualmente o generata automaticamente potrebbe rivelarsi una soluzione migliore rispetto a un punto di riferimento del servizio (che sta usando un Solitamente la libreria SI ti obbliga a trasmettere dati addizionali con la libreria SI di quanto tu ne abbia necessariamente bisogno).

Caso 1

Per le dipendenze, che hanno determinate configurazioni che non cambiano durante il processo di applicazione o quando vengono cambiate le modifiche devono riflettere sull'intera applicazione, il localizzatore di servizi è in realtà un'idea abbastanza fattibile.

Esempi di alcune di queste classi possono essere:

  • database
  • caching
  • una fonte per i servizi web.

Di solito sono le impostazioni globali dell'applicazione, che, una volta modificate, influiscono in modo drastico sul grafico degli oggetti.

Ad esempio, se la tua configurazione dice che stai usando un MongoDB e cambi la configurazione per usare MySQL, è molto probabile che tu usi MySQL per l'intera applicazione, non solo per una certa parte e vuoi il tuo intero applicazione per sapere di estrarre i dati da MySQL ora e ignorare Mongo.

Caso 2

Quando è necessario costruire un'implementazione di un'interfaccia generale durante un runtime in base ai risultati dei dati elaborati in precedenza o dell'input dell'utente, probabilmente si vorrà creare il proprio factory con un interruttore / caso che fornisca un'implementazione specifica basata su il valore che viene passato al metodo factory.

In questo modo non stai trasferendo codice non necessario dal localizzatore di servizio, ma disponi di una fabbrica molto semplice e leggera.

Un esempio di un caso in cui questo tipo di factory verrebbe utilizzato è quando si vogliono estrarre dati da due classi, che implementano la stessa interfaccia ma in più hanno dati aggiuntivi e sono costruiti in modi completamente diversi e diversi i servizi devono essere forniti per recuperarli.

Un esempio potrebbe essere un ciclo foreach su una raccolta che fornirebbe il flag per la decisione, che l'implementazione utilizzerà. All'interno di questo foreach , si passa il valore della singola iterazione alla factory, che costruisce un'implementazione specifica ma con l'interfaccia condivisa, che è possibile utilizzare per recuperare i dati comuni per tutti i valori nella raccolta che si sta correndo foreach su.

    
risposta data 29.04.2016 - 16:47
fonte

Leggi altre domande sui tag