Qualcuno ha un buon pattern di registrazione DI?

5

L'ho fatto così tante volte ma non finisco mai per apprezzare il modo in cui il mio codice risulta. Sto chiedendo se qualcun altro ha gli stessi problemi e può offrire soluzioni o convenzioni che ha usato per rendere il codice più pulito.

Quando costruisco un servizio C #, ottengo TopShelf, Log4Net e Ninject tutti installati e configurati. All'interno del mio progetto di servizio, finisco con una classe come questa. La parte a cui sono più interessato è SuperLargeStaticClassThing.RegisterDependencies(_kernel);

sealed class ServiceMain
{
    [NotNull]
    private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    private IKernel _kernel;

    /// <summary>   Starts the service.</summary>
    public void Start()
    {
        Log.Info("Service Start");

        // Step 1: Register Ninject Dependencies
        _kernel = new StandardKernel();
        SuperLargeStaticClassThing.RegisterDependencies(_kernel);

        // Step 2: Start Service Endpoints
        _kernel.Get<IEndpoint1>().Start();
        // ...
    }

    /// <summary>   Stops the service.</summary>
    public void Stop()
    {
        Log.Info("Service Stop");

        // Stop Service Endpoints
        _kernel.Get<IEndpoint1>().Stop();
        // ...
    }
}

E il mio metodo SuperLargeStaticClassThing.RegisterDependencies(_kernel); finisce per essere enorme, brutto, monolitico, tu lo chiami. Sono centinaia di linee di interfacce solo vincolanti per altri tipi. A volte puoi farcela con la registrazione basata sulla convenzione per renderla più breve, ma credo che anche questa non sia sempre una soluzione. Non solo il metodo è enorme, sembra poco pratico. A volte alcune dipendenze devono essere scambiate in blocchi, quindi mi piacerebbe vedere le diverse categorie di dipendenze registrate insieme. Sento che ci deve essere un modo migliore, ma sono bloccato nel trovarne uno che mi piace.

Qualcuno ha qualche suggerimento su un modello per risolvere il problema di un metodo di registrazione enorme e poco maneggevole?

    
posta Frank Bryce 18.05.2016 - 16:34
fonte

4 risposte

5

so I'd enjoy seeing the different categories of dependencies registered together.

Ninject fornisce il concetto di un modulo per consentire di raggruppare i collegamenti correlati in una singola classe.

link

class WeaponsModule : NinjectModule
{
    private readonly bool useMeleeWeapons;
    public WeaponsModule(bool useMeleeWeapons) {
        this.useMeleeWeapons = useMeleeWeapons;
    }

    public void Load()
    {
        if (this.useMeleeWeapons)
            Bind<IWeapon>().To<Sword>();
        else
            Bind<IWeapon>().To<Shuriken>();
    }
}

class Program
{
    public static void Main()
    {
        bool useMeleeWeapons = false;
        IKernel kernel = new StandardKernel(new WeaponsModule(useMeleeWeapons));
        Samurai warrior = kernel.Get<Samurai>();
        warrior.Attack("the evildoers");
    }
}
    
risposta data 18.05.2016 - 20:51
fonte
2

Il modo in cui risolviamo questo problema dove lavoro è legando le interfacce attraverso la riflessione.

Ogni classe è decorata con un attributo che specifica quale contratto di interfaccia soddisfa la classe. Quando l'applicazione viene caricata, il nostro contenitore DI (chiamato "Creatore") si riflette sull'intero assieme e memorizza tutte queste coppie di interfaccia / tipo in un dizionario.

Quando viene chiamato Creator.Make<Iinterface>() , recupera il primo tipo dal dizionario, percorre l'albero del costruttore cercando tutti i tipi necessari, li istanzia in ordine e collega automaticamente tutto. C'è una disposizione per specificare i parametri concreti, dove sono necessari.

Tutte le classi di implementazione sono contrassegnate con internal , che "incoraggia" tutti a utilizzare il metodo Make invece di creare un'istanza diretta delle classi.

    
risposta data 18.05.2016 - 18:26
fonte
2

MEF it.

Costruito nel framework .NET è uno spazio dei nomi spesso sovrascritto, creato principalmente per gestire il supporto dei plugin: System.ComponentModel.Composition - noto anche come MEF.

MEF può essere applicato in modo molto efficace per DI, possibilmente fornendo esattamente quello che stai cercando.

Per prima cosa, decori una classe con gli attributi necessari:

[Export] // This attribute specifies that a given class should be created and used in composition.
[PartCreationPolicy(CreationPolicy.NonShared)] // By default, one instance of the class will be shared with all importing classes. This is how you tel it you want something else.
public class Thing
{
  private readonly IDependency yourDependency;

  [ImportingConstructor] // This must be placed on the non-primary constructor you want to use, or the primary constructor will be used.
  public Thing(IDependency theThingYouWantToImport)
  {
    yourDependency = theThingYouWantToImport;
  }
}

[Export(typeof(IDependency))] // This tells MEF that you want your export to fill your chosen interface, rather than its own type.
public class Dependency { }

Da qualche altra parte, nella tua root di composizione, devi creare il tuo contenitore:

// You may also have other catalogs, like DirectoryCatalog, if you want to support plugins. If you only have one, you don't need the aggregate at all.
var assemblyCatalog = new AssemblyCatalog(typeof(App).Assembly);
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(assemblyCatalog);
var container = new CompositionContainer(catalog);
container.ComposeParts();

A questo punto, puoi richiedere una delle classi dal tuo grafo di oggetti completamente costruito.

C'è molto più disponibile di questo: MEF supporta ExportFactory<T> , così come i metodi di chiamata quando tutte le importazioni sono soddisfatte, e cose più complicate come l'esportazione con metadati aggiuntivi.

Speriamo che questo sia ciò di cui hai bisogno e abbastanza per iniziare.

    
risposta data 20.05.2016 - 21:22
fonte
0

Questo è quello che ho scoperto sulla base della risposta già da @Robert Harvey, che funzionerà per almeno il 90% dei casi d'uso di cui ho bisogno. Questo declutterà sicuramente la procedura di registrazione. La configurazione di se registrare un tipo e la durata degli oggetti creati sono controllati dai file di classe / interfaccia e non dal metodo di registrazione monolitica ora.

Idea

Prima di mostrare l'implementazione che mi è venuta in mente, sappi che è piccola e che c'è sicuramente spazio per miglioramenti. Questa è una sorta di linea di base che modificherò man mano che procedo. L'idea di base è che creerò attributi personalizzati da inserire nelle classi che voglio registrare con DI. Quindi attraverso la riflessione, il processo di registrazione cerca questi attributi e registra il tipo in base alle informazioni ottenute dagli attributi sulle classi e / o sulle interfacce stesse. Questo ha due grandi vantaggi e uno svantaggio.

Vantaggi:

  1. Le registrazioni semplici avvengono in modo distribuito. Non esiste più un luogo in cui avvenga la registrazione, quindi è più facile da gestire e più facile trovare ciò che stai cercando
  2. Ogni volta che viene aggiunta una nuova dipendenza, non è necessario mantenere aperto il metodo di registrazione. Basta aggiungere l'attributo alla classe che stai aggiungendo comunque ed è l'auto "magic" alleata registrata.

Lato negativo: per registrazioni più complicate, che possono essere basate sulla configurazione, non sono gestite con la soluzione che ho dato. Per queste soluzioni potrei aggiungerne altre con il passare del tempo, ma nel frattempo penso che questo sia un buon punto di partenza.

Codice

Ho cambiato il nome di SuperLargeStaticClassThing in SmallerStaticClassThing dall'OP. Anche se sto usando Ninject qui, le funzionalità che sto usando sono disponibili in tutti i pacchetti DI che ho usato.

static class SmallerStaticClassThing
{
    internal static void RegisterDependencies(IKernel kernel)
    {
        var concreteTypesToRegister = Assembly.GetExecutingAssembly().GetTypes()
            .Where(x => x.IsClass && 
                       !x.IsAbstract &&  
                        x.GetCustomAttributes(typeof (RegisterAttribute)).Any());
        foreach (var concrete in concreteTypesToRegister)
        {
            var interfacesToRegister = concrete.GetInterfaces()
                .Where(x => !x.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any());
            foreach (var @interface in interfacesToRegister)
            {
                var binding = kernel.Bind(@interface).To(concrete);
                if (concrete.GetCustomAttributes(
                    typeof (SingletonLifetimeAttribute)).Any())
                {
                    binding.InSingletonScope();
                }
                else if (concrete.GetCustomAttributes(
                    typeof (TransientLifetimeAttribute)).Any())
                {
                    binding.InTransientScope();
                }
                else if (concrete.GetCustomAttributes(
                    typeof(ThreadLifetimeAttribute)).Any())
                {
                    binding.InThreadScope();
                }
                else
                {
                    // default scope... nothing needed for Ninject
                }
            }
        }

        // ... other stuff if you need
    }
}

Questo codice richiede alcuni attributi personalizzati che ho creato rapidamente. Sembrano tutti fondamentalmente simili, con il nome di riepilogo / classe diverso.

/// <summary>
/// Put this Attrubute on your class if you want the type to be instantiated
/// with a singleton lifetime management strategy
/// </summary>
[AttributeUsage(validOn: AttributeTargets.Class | AttributeTargets.Interface)]
public class SingletonLifetimeAttribute : Attribute
{
}

Un rapido esempio di una classe che mi piacerebbe registrare con qualsiasi servizio in cui sia incluso è il seguente.

[Register, SingletonLifetime]
class HelpfulDependencyImplementation : IHelpfulDependeny
{
    // ... insert implementation here
}

Questo funzionerà per me, penso, ma il tempo lo dirà. Sono sicuro che piccole modifiche avverranno nel tempo per consentire una configurazione DI più ampia utilizzando gli attributi.

Il feedback è apprezzato.

    
risposta data 18.05.2016 - 20:28
fonte

Leggi altre domande sui tag