수색…


비고

DI (Dependency Injection)의 전체 요점은 코드 결합을 줄이는 것입니다. "하드 코딩 된 종속성 예제"와 같은 새로운 것을 포함하는 상호 작용이 있으면 어떤 종류의 상상력이라도 상상해보십시오.

코드 작성의 큰 부분은이를 테스트하는 것입니다. 새로운 의존성을 새로이 시작할 때마다 우리는 의존성에 대한 통제권이 없기 때문에 코드를 테스트하기가 어렵습니다.

DataTime에 의존하는 코드를 어떻게 테스트 할 것입니까? 예를 들어? 항상 변경되므로 참조가 없습니다. 이것은 출발점으로 안정적인 매개 변수를 주입 할 때입니다. 당신이 그것을 제어 할 수 있습니다, 당신은 다양한 가치에 따라 테스트를 작성하고 항상 올바른 결과를 얻을 수 있는지 확인하십시오.

따라서 좋은 옵션은 생성자 DI에서 인터페이스 또는 추상 클래스를 매개 변수로 전달하는 것입니다.

인터페이스는 잘 정의 된 계약을 나타내며 항상 거기에있는 메소드에 의존 할 수 있으며 메소드 서명에 항상 의지 할 수 있습니다.

DI 사용을 시작하면 다른 측면이 열리게됩니다. 예를 들어 어떤 시점에서 인터페이스를 전달하더라도 실제로 어떤 작업을 수행하려면 실제 구현이 필요합니다. 여기에 다른 개념이 나타납니다. IOC (Inversion of Control)를 사용하여 종속성을 해결할 수 있습니다. 즉, 계약에 대해 항상 특정 구현을 사용하도록 코드에 지시합니다. 물론이 작업을 수행하는 다른 방법이 있습니다. 우리는 항상 특정 구현체로 각 계약을 인스턴스화 할 수 있으며 그 시점부터 우리 코드는 그 부분을 사용할 수 있습니다.

public ILogging Logging { get; set }

어떤 시점에서 우리는 그것을 초기화한다.

Logging = new FileLogging();

이것은 우리 학급이 예상 계약을 이행하는 한 항상 효과가 있습니다.

public class FileLogging : ILogging

초기화 순간부터 우리는 항상 로깅 객체를 사용합니다. 예를 들어 DatabaseLogging을 변경하고 사용하기로 결정한 경우 코드를 한 곳에서 변경하면되기 때문에 Logging 클래스를 초기화하는 것이기 때문에 수명이 더 쉽습니다.

DI는 테스트 용으로 만 좋은가? 아니요, DI는 유지 보수 가능한 코드를 작성할 때도 중요합니다. 우려 사항을 명확히 구분할 수 있습니다.

어떤 코드를 작성할 때, 생각해보십시오 ... 테스트 할 수 있습니까? 테스트를 작성할 수 있습니까? DateTime을 사용하는 대신 DateTime 값을 주입 할 때입니다. 이제 의미가 있습니다.

Ninject 구성

IoC (Inversion of Control) 컨테이너를 설치 한 후 제대로 작동하려면 몇 가지 조정이 필요합니다. 이 경우 Ninject를 사용합니다. App_Start 폴더에있는 NinjectWebCommon 파일에서 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;
        }
    }

RegisterServices 메소드는 다음을 사용합니다.

 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;
        } 

이 경우 Container라는 바인딩에 대한 새 클래스를 만듭니다.

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());
    }
}

마지막으로 파생 된 각 NinjectModule 클래스에서 다음과 같이 Load 메서드를 오버로드하는 바인딩을 수정합니다.

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();
    }
}

파생 된 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>();           
    }
}

인터페이스 사용

서비스가 필요한 구체적인 클래스에서 다음과 같은 구현 대신 인터페이스를 사용하여 서비스에 액세스하십시오.

 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();
    }        
}

이제 구체적인 클래스에서 무언가가 필요한 경우 위의 코드를 방해하지 않습니다. 다른 구현을 위해 서비스 구현을 변경할 수도 있고, 인터페이스가 만족 스러울수록 길어질 수 있습니다. 또한 테스트하기가 매우 쉽습니다.

생성자 의존성 삽입

Constructor Dependency Injection은 의존성을 주입하기 위해 생성자의 매개 변수를 필요로합니다. 따라서 새 객체를 만들 때 값을 전달해야합니다.

public class Example
{
    private readonly ILogging _logging;

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

하드 코딩 된 종속성

public class Example
{
    private FileLogging _logging;

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

매개 변수 DI

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

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

사후 의존성 주입

의존성 분석기는 밀접하게 결합 된 클래스를 피하고 유연성을 향상 시키며 테스트를 쉽게하기 위해 사용됩니다. 자신 만의 의존성 인젝터 (권장하지 않음)를 만들거나 잘 작성되고 테스트 된 의존성 인젝터 중 하나를 사용할 수 있습니다. 이 예에서는 Ninject 를 사용할 것입니다.

1 단계 : 의존성 분석자를 만듭니다.

우선, NuGet에서 Ninject 를 다운로드하십시오. Infrastructure 라는 폴더를 만들고 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
    }
}

MVC 프레임 워크는 들어오는 요청을 처리하기 위해 클래스의 기능이 필요할 때 GetServiceGetServices 메소드 호출합니다.

2 단계 : 의존성 해결 자 등록.

이제 우리는 커스텀 의존성 리졸버 (dependency resolver)를 가지고 있으며 MVC 프레임 워크가 의존성 리졸버 (dependency resolver)를 사용하도록하기 위해 그것을 등록해야합니다. Global.asax.cs 파일에 종속성 해결 프로그램 등록 :

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

3 단계 : 바인딩 추가.

우리가 다음과 같은 인터페이스와 암시를 가지고 있다고 상상해보십시오 :

public interface ICustomCache
{
    string Info { get; }
}

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

우리 컨트롤러를 CustomCache와 긴밀하게 연결하지 않고 컨트롤러에서 CustomCache를 사용하려면 ICustomCache를 CustomCache 에 바인드하고 Ninject를 사용하여 주입해야합니다. 것부터 먼저 NinjectDependencyResolver 방법) (AddBindings에 다음의 코드를 추가하여 CustomCache에 ICustomCache 바인딩 :

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

다음과 같이 주입을 위해 컨트롤러를 준비하십시오.

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();
    }
}

이것은 costructor injection의이며 종속성 주입의 한 형태입니다 . 아시다시피 우리 홈 컨트롤러는 CustomCache 클래스 자체에 의존하지 않습니다. 응용 프로그램에서 ICustomCache의 또 다른 구현을 사용하려는 경우 변경해야 할 것은 ICustomCache 를 다른 implentation에 바인딩하는 것 뿐이며 취할 수있는 유일한 단계입니다. 여기서 일어난 일은 MVC Framework가 등록 된 의존성 해결 자 에게 GetService 메소드를 통해 HomeController 클래스의 인스턴스를 생성하도록 요청했습니다. 의 getService 방법은 요청 객체를 생성해서 Ninject 커널을 물어 Ninject에 커널은 그 용어의 유형을 검사하고 HomeController의 생성자가 ICustomCache을 requeires 이미 ICustomCache에 추가되었습니다 바인딩 것을 발견한다. Ninject는 바인드 된 클래스의 인스턴스를 생성하고이를 사용하여 HomeController 를 생성하고 MVC 프레임 워크를 반환합니다.

종속성 체인.

Ninject가 타입을 만들려고 할 때, 타입과 다른 타입 사이의 다른 숙제를 검사하고 어떤 Ninject가 그것을 만들려고하는지 또한 검사합니다. 예를 들어, CustomCache 클래스에 ICacheKeyProvider가 필요하고 ICinKeyProvider에 대해 bining이 추가되면 Ninject가 클래스에이를 제공 할 수 있습니다.

ICacheKeyProvider 인터페이스 및 SimpleCacheKeyProvider implentation :

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);
    }
}

수정 된 CustomCache 클래스

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

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

        CacheKeyProvider = keyProviderParam;
    }

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

ICacheKeyProvider에 대한 바인딩 추가 :

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

우리가 HomeController Ninject에로 이동하면 SimpleCacheKeyProvider이 CustomCache를 생성하는 데 사용하고 HomeController를 만들 CustomCache 인스턴스를 사용하는 지금의 인스턴스를 만듭니다.

Ninject에는 체인 종속성 삽입과 같은 많은 기능이 있으므로 Ninject를 사용하려는 경우 검사해야합니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow