Szukaj…


Uwagi

Cały sens wtrysku zależności (DI) polega na zmniejszeniu sprzężenia kodu. Wyobraź sobie jakąkolwiek interakcję, która wymaga odnowienia czegoś takiego jak w „Przykładach zależności kodowanych na stałe”.

Dużą częścią pisania kodu jest możliwość jego przetestowania. Za każdym razem, gdy odkrywamy nową zależność, utrudniamy testowanie naszego kodu, ponieważ nie mamy kontroli nad tą zależnością.

Jak przetestowałbyś kod, który zależy na przykład od DataTime.Now? Zawsze się zmienia, więc nie masz odniesienia. To wtedy wstrzykujesz stabilny parametr jako punkt początkowy. Możesz to kontrolować, pisać testy oparte na różnych wartościach i mieć pewność, że zawsze uzyskasz właściwy wynik.

Dlatego dobrą opcją jest przekazanie interfejsu lub klasy abstrakcyjnej jako parametru w konstruktorze DI.

Interfejs reprezentuje dobrze zdefiniowaną umowę, zawsze możesz polegać na dostępnych metodach i zawsze możesz polegać na sygnaturach metod.

Gdy zaczniesz używać DI, otworzą się inne aspekty. Na przykład, nawet jeśli w pewnym momencie przejdziesz przez interfejs, będziesz potrzebować prawdziwej implementacji, aby faktycznie wykonać jakąkolwiek pracę. Tutaj pojawiają się inne koncepcje. Możemy użyć IOC (Inversion of Control), aby rozwiązać nasze zależności. Oznacza to, że instruujemy nasz kod, aby zawsze używał określonej implementacji dla każdej umowy. Oczywiście są na to inne sposoby. Zawsze mogliśmy utworzyć każdą umowę z konkretną implementacją i od tego momentu nasz kod może korzystać z tej części:

public ILogging Logging { get; set }

w pewnym momencie inicjalizujemy to.

Logging = new FileLogging();

to zawsze będzie działać, dopóki nasza klasa spełni oczekiwany kontrakt:

public class FileLogging : ILogging

od momentu inicjalizacji zawsze korzystamy z obiektu Logging. To sprawia, że życie jest łatwiejsze, ponieważ jeśli kiedykolwiek zdecydujemy się zmienić i użyć na przykład DatabaseLogging, musimy zmienić kod tylko w jednym miejscu i właśnie tam inicjujemy klasę Logging.

Czy DI nadaje się tylko do testowania? Nie, DI jest również ważne przy pisaniu możliwego do utrzymania kodu. Pozwala to na wyraźne rozdzielenie obaw.

Kiedy piszesz jakikolwiek kod, pomyśl ... czy można go przetestować, czy mogę napisać test, to wtedy wstrzyknięcie wartości DateTime zamiast użycia DateTime.Now ma sens.

Konfiguracje Ninject

Po zainstalowaniu kontenera IoC (Inversion of Control) konieczne są pewne poprawki, aby działał. W tym przypadku użyję Ninject. W pliku NinjectWebCommon, który znajduje się w folderze App_Start, zastąp metodę CreateKernel:

private static IKernel CreateKernel()
    {  
        // Create the kernel with the interface to concrete bindings           
        var kernel = RegisterServices();
        try
        {
            kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
            kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
           
            return kernel;
        }
        catch
        {
            kernel.Dispose();
            throw;
        }
    }

I metoda RegisterServices z:

 private static StandardKernel RegisterServices()
        {
            Container container = new Container();
            // encapsulate the interface to concrete bindings in another class or even in another layer 
            StandardKernel kernel = container.GetServices();
            return kernel;
        } 

Utwórz nową klasę dla powiązania, które w tym przypadku nazywa się Kontener:

public class Container
{
    public StandardKernel GetServices()
    {
        // It is good practice to create a derived class of NinjectModule to organize the binding by concerns. In this case one for the repository, one for service and one for app service bindings
        return new StandardKernel(new NinjectRepositoryModule(),
            new NinjectServiceModule(),
            new NinjectAppServiceModule());
    }
}

Na koniec w każdej pochodnej klasie NinjectModule zmodyfikuj powiązania przeciążające metodę Load, takie jak:

public class NinjectRepositoryModule: NinjectModule
{
    public override void Load()
    {
        // When we need a generic IRepositoryBase<> to bind to a generic RepositoryBase<>
        // The typeof keyword is used because the target method is generic
        Bind(typeof (IRepositoryBase<>)).To(typeof (RepositoryBase<>));

        // When we need a IUnitOfWorkbind to UnitOfWork concrete class that is a singleton
        Bind<IUnitOfWork>().To<UnitOfWork>().InSingletonScope();
    }
}

Kolejny przykład pochodnego NinjectModule:

public class NinjectServiceModule :NinjectModule
{
    public override void Load()
    {
        // When we need a IBenefitService to BenefitService concrete class           
        Bind<IBenefitService>().To<BenefitService>();

        // When we need a ICategoryService to CategoryService concrete class  
        Bind<ICategoryService>().To<CategoryService>();

        // When we need a IConditionService to ConditionService concrete class  
        Bind<IConditionService>().To<ConditionService>();           
    }
}

Wykorzystanie interfejsów

W konkretnej klasie, która potrzebuje usługi, użyj interfejsu, aby uzyskać dostęp do usługi zamiast jej implementacji, takich jak:

 public class BenefitAppService
{
    private readonly IBenefitService _service;
    public BenefitAppService(IBenefitService service)
    {
        _service = service;
    }      
   
    public void Update(Benefit benefit)
    {
        if (benefit == null) return           
        _service.Update(benefit);
        _service.Complete();
    }        
}

Teraz, jeśli potrzebujesz czegoś w konkretnej klasie, nie będzie ingerował w powyższy kod. Możesz zmienić implementację usługi na inną całkowicie różnicę, i dopóki jest ona zgodna z interfejsem, możesz przejść. Również bardzo łatwo to przetestować.

Zastrzyk zależności konstruktora

Wstrzykiwanie zależności przez konstruktora wymaga parametrów w konstruktorze, aby wstrzyknąć zależności. Musisz więc przekazać wartości podczas tworzenia nowego obiektu.

public class Example
{
    private readonly ILogging _logging;

    public Example(ILogging logging)
    {
        this._logging = logging;
    }
}

Zależność zakodowana na stałe

public class Example
{
    private FileLogging _logging;

    public Example()
    {
        this._logging = new FileLogging();
    }
}

parametr DI

public DateTime SomeCalculation()
{
     return DateTime.Now.AddDays(3);
}

vs

public DateTime SomeCalculation(DateTime inputDate)
{
    return inputDate.AddDays(3);
}

Zastrzyk Ninject Dependency

Moduł rozwiązywania zależności służy do unikania ściśle powiązanych klas, poprawy elastyczności i ułatwienia testowania. Możesz stworzyć swój własny wtryskiwacz zależności (niezalecany) lub użyć jednego z dobrze napisanych i przetestowanych wtryskiwaczy zależności. W tym przykładzie użyję Ninject .

Krok pierwszy: Utwórz narzędzie do rozwiązywania zależności.

Przede wszystkim pobierz Ninject z NuGet. Utwórz folder o nazwie Infrastruktura i dodaj klasę o nazwie NinjectDependencyResolver :

using Ninject;
using System;
using System.Collections.Generic;
using System.Web.Mvc;

public class NinjectDependencyResolver
    : IDependencyResolver
{
    private IKernel kernel;

    public NinjectDependencyResolver()
    {
        // Initialize kernel and add bindings
        kernel = new StandardKernel();
        AddBindings();
    }

    public object GetService(Type serviceType)
    {
        return kernel.TryGet(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return kernel.GetAll(serviceType);
    }

    private void AddBindings()
    {
        // Bindings added here
    }
}

Framework MVC wywoła metody GetService i GetServices , gdy potrzebuje ubezpieczenia klasy do obsługi przychodzącego żądania.

Krok drugi: Zarejestruj przelicznik zależności.

Teraz mamy nasz niestandardowy program do rozwiązywania zależności i musimy go zarejestrować, aby poinformować środowisko MVC o użyciu naszego programu do rozwiązywania zależności. Zarejestruj przelicznik zależności w pliku Global.asax.cs :

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    DependencyResolver.SetResolver(new NinjectDependencyResolver());
    
    // .....
}

Krok trzeci: Dodaj powiązania.

Wyobraź sobie, że mamy następujący interfejs i implementację:

public interface ICustomCache
{
    string Info { get; }
}

public class CustomCache : ICustomCache
{
    public string Info
    {
        get
        {
            return "Hello from CustomCache.";
        }
    }
}

Jeśli chcemy użyć CustomCache w naszym kontrolerze bez ścisłego powiązania naszego kontrolera z CustomCache, musimy powiązać ICustomCache z CustomCache i wstrzyknąć go za pomocą Ninject. Po pierwsze, powiąż ICustomCache z CustomCache , dodając następujący kod do metody AddBindings () NinjectDependencyResolver :

private void AddBindings()
{
    // Bindings added here
    kernel.Bind<ICustomCache>().To<CustomCache>();
}

Następnie przygotuj kontroler do wstrzyknięcia, jak poniżej:

public class HomeController : Controller
{
    private ICustomCache CustomCache { get; set; }

    public HomeController(ICustomCache customCacheParam)
    {
        if (customCacheParam == null)
            throw new ArgumentNullException(nameof(customCacheParam));

        CustomCache = customCacheParam;
    }

    public ActionResult Index()
    {
        // cacheInfo: "Hello from CustomCache."
        string cacheInfo = CustomCache.Info;

        return View();
    }
}

To jest przykład wstrzyknięcia costructora i jest to jedna z form wstrzyknięcia zależności . Jak widać, nasz kontroler domowy nie zależy od klasy CustomCache. Jeśli chcemy skorzystać z innej aplikacji ICustomCache w naszej aplikacji, jedyną rzeczą, którą musimy zmienić, jest powiązanie ICustomCache z inną implantacją i jest to jedyny krok, który musimy podjąć. Co tu się stało jest MVC Framework poprosił naszą zarejestrowaną zależnościach resolverowi aby utworzyć instancję klasy HomeController poprzez metody getService. Metoda GetService prosi jądro Ninject o utworzenie żądanego obiektu, a jądro Ninject bada typ w swoim terminie i dowiaduje się, że konstruktor HomeController wymaga ICustomCache, a dla ICustomCache zostało już dodane powiązanie . Ninject tworzy instancję powiązanej klasy , używa jej do tworzenia HomeController i zwraca ją MVC Framework.

Łańcuchy zależności.

Gdy Ninject próbuje utworzyć typ, sprawdza inne zależności między typem a innymi typami, a jeśli istnieje, Ninject próbuje je również utworzyć. Na przykład, jeśli nasza klasa CustomCache wymaga ICacheKeyProvider i jeśli dodawanie binarne dla ICacheKeyProvider Ninject może zapewnić ją dla naszej klasy.

Interfejs ICacheKeyProvider i implementacja SimpleCacheKeyProvider :

public interface ICacheKeyProvider
{
    string GenerateKey(Type type);
}

public class SimpleCacheKeyProvider
    : ICacheKeyProvider
{
    public string GenerateKey(Type type)
    {
        if (type == null)
            throw new ArgumentNullException(nameof(type));

        return string.Format("{0}CacheKey", type.Name);
    }
}

Zmodyfikowana klasa CustomCache

public class CustomCache : ICustomCache
{
    private ICacheKeyProvider CacheKeyProvider { get; set; }

    public CustomCache(ICacheKeyProvider keyProviderParam)
    {
        if (keyProviderParam == null)
            throw new ArgumentNullException(nameof(keyProviderParam));

        CacheKeyProvider = keyProviderParam;
    }

    ...........
}

Dodaj powiązanie dla ICacheKeyProvider :

private void AddBindings()
{
    // Bindings added here
    kernel.Bind<ICustomCache>().To<CustomCache>();
    kernel.Bind<ICacheKeyProvider>().To<SimpleCacheKeyProvider>();
}

Teraz, gdy przejdziemy do HomeController Ninject tworzy instancję SimpleCacheKeyProvider używa jej do tworzenia CustomCache i używa instancji CustomCache do utworzenia HomeController .

Ninject ma wiele wspaniałych funkcji, takich jak wstrzykiwanie zależności zależnej i powinieneś je sprawdzić, jeśli chcesz użyć Ninject.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow