의존성 주입을 받았지만 누군가 IoC 컨테이너의 필요성을 이해하도록 도울 수 있습니까?


15

이것이 또 다른 질문의 반복처럼 보이는 경우에 사과하지만, 주제에 관한 기사를 찾을 때마다 대부분 DI에 대해 이야기합니다. 그래서 나는 DI를 얻지 만 모든 사람들이 들어가는 것처럼 보이는 IoC 컨테이너의 필요성을 이해하려고합니다. IoC 컨테이너의 요점은 실제로 종속성의 구체적인 구현을 "자동 해결"하는 것입니까? 어쩌면 내 클래스에는 몇 가지 종속성이없는 경향이 있으며 그로 인해 큰 문제는 보이지 않지만 컨테이너의 유틸리티를 올바르게 이해하고 있는지 확인하고 싶습니다.

나는 일반적으로 비즈니스 로직을 다음과 같은 클래스로 나눕니다.

public class SomeBusinessOperation
{
    private readonly IDataRepository _repository;

    public SomeBusinessOperation(IDataRespository repository = null)
    {
        _repository = repository ?? new ConcreteRepository();
    }

    public SomeType Run(SomeRequestType request)
    {
        // do work...
        var results = _repository.GetThings(request);

        return results;
    }
}

따라서 하나의 의존성 만 있으며 경우에 따라 두 번째 또는 세 번째가있을 수 있지만 그다지 자주는 아닙니다. 따라서 이것을 호출하는 것은 자체 저장소를 전달하거나 기본 저장소를 사용하도록 허용 할 수 있습니다.

현재 IoC 컨테이너에 대한 이해는 IDataRepository를 해결하는 것입니다. 그러나 그것이 전부라면, 나는 의존성이 전달되지 않을 때 내 운영 클래스가 이미 폴백을 정의하기 때문에 많은 가치를 보지 못합니다. 따라서 내가 생각할 수있는 유일한 다른 이점은 이것은 동일한 대체 저장소를 사용하므로 레지스트리 / 공장 / 컨테이너 인 한 곳에서 해당 저장소를 변경할 수 있습니다. 그리고 그것은 훌륭하지만 그게 맞습니까?


1
기본 폴백 버전의 종속성을 갖는 것이 실제로 의미가없는 경우가 종종 있습니다.
Ben Aaronson

무슨 말이에요? "대체"는 단위 테스트를 제외하고 거의 항상 사용되는 구체적인 클래스입니다. 실제로 이것은 컨테이너에 등록 된 것과 동일한 클래스입니다.
Sinaesthetic

예. 그러나 컨테이너의 경우 : (1) 컨테이너의 다른 모든 객체는 동일한 인스턴스를 가져 ConcreteRepository오고 (2) 추가 종속성을 제공 할 수 있습니다 ConcreteRepository(예 : 데이터베이스 연결이 일반적 임).
Jules

@Sinaesthetic 항상 나쁜 생각은 아니지만 종종 적절하지 않다는 것을 의미하지는 않습니다. 예를 들어 프로젝트 참조로 양파 아키텍처를 따르는 것을 방지 할 수 있습니다. 또한 명확한 기본 구현이 없을 수 있습니다. 쥘 말한대로 그리고, IOC 컨테이너는 종속 유형을 따기되지 관리하지만, 공유 인스턴스와 라이프 사이클 관리 등의 작업을 수행
벤 아 론슨

"함수 매개 변수-ORIGINAL Dependency Injection!"이라고 쓰여진 tshirt를받을 것입니다.
Graham

답변:


2

IoC 컨테이너는 하나의 의존성이있는 경우가 아닙니다. 3 개의 종속성이 있고 종속성 등이있는 여러 종속성이있는 경우에 관한 것입니다.

또한 종속성 해결 및 종속성 수명주기 관리를 중앙 집중화하는 데 도움이됩니다.


10

IoC 컨테이너를 사용하려는 데는 여러 가지 이유가 있습니다.

참조되지 않은 dll

IoC 컨테이너를 사용하여 참조되지 않은 dll에서 구체적인 클래스를 확인할 수 있습니다. 즉, 추상화, 즉 인터페이스에 전적으로 의존 할 수 있습니다.

사용을 피하십시오 new

IoC 컨테이너 new는 클래스 생성 을 위해 키워드 사용을 완전히 제거 할 수 있음을 의미합니다 . 이것은 두 가지 효과가 있습니다. 첫 번째는 클래스를 분리시키는 것입니다. 두 번째 (관련)는 단위 테스트를 위해 모의를 넣을 수 있다는 것입니다. 이는 특히 장기 실행 프로세스와 상호 작용할 때 매우 유용합니다.

추상화에 대하여 쓰기

IoC 컨테이너를 사용하여 구체적인 종속성을 해결하면 필요한 모든 구체적인 클래스를 구현하는 대신 추상화에 대해 코드를 작성할 수 있습니다. 예를 들어, 데이터베이스에서 데이터를 읽으려면 코드가 필요할 수 있습니다. 데이터베이스 상호 작용 클래스를 작성하는 대신 단순히 인터페이스를 작성하고 이에 대한 코드를 작성하면됩니다. 모의를 사용하여 다른 코드를 테스트하기 전에 구체적인 데이터베이스 상호 작용 클래스를 개발하지 않고 개발중인 코드의 기능을 테스트 할 수 있습니다.

취성 코드를 피하십시오

IoC 컨테이너를 사용하는 또 다른 이유는 IoC 컨테이너를 사용하여 종속성을 해결함으로써 종속성을 추가하거나 제거 할 때 클래스 생성자에 대한 모든 단일 호출을 변경할 필요가 없기 때문입니다. IoC 컨테이너는 자동으로 종속성을 해결합니다. 클래스를 한 번 만들 때 큰 문제는 아니지만 백 곳에서 클래스를 만들 때 큰 문제입니다.

평생 관리 및 관리되지 않는 저장소 정리

마지막으로 언급 할 이유는 객체 수명 관리입니다. IoC 컨테이너는 종종 객체의 수명을 지정하는 기능을 제공합니다. 코드에서 수동으로 관리하는 대신 IoC 컨테이너에서 객체의 수명을 지정하는 것이 좋습니다. 수동 수명 관리는 매우 어려울 수 있습니다. 이것은 폐기가 필요한 물체를 다룰 때 유용 할 수 있습니다. 일부 IoC 컨테이너는 개체 처리를 수동으로 관리하는 대신 처리를 관리하므로 메모리 누수를 방지하고 코드베이스를 단순화 할 수 있습니다.

제공 한 샘플 코드의 문제점은 작성중인 클래스가 ConcreteRepository 클래스에 구체적으로 의존한다는 것입니다. IoC 컨테이너는 해당 종속성을 제거합니다.


22
이것들은 IoC 컨테이너의 장점이 아니며, 의존성 주입의 장점이며, 가난한 사람의 DI로 쉽게 수행 할 수 있습니다
Ben Aaronson

IoC 컨테이너없이 좋은 DI 코드를 작성하는 것은 실제로 어려울 수 있습니다. 그렇습니다. 장점에는 겹치는 부분이 있지만, IoC 컨테이너에서 가장 잘 활용되는 장점입니다.
Stephen

글쎄, 내 의견 이후에 추가 한 마지막 두 가지 이유는 컨테이너에 따라
다르며

"신규 사용을 피하십시오"-정적 코드 분석을 방해하므로 hmemcpy.github.io/AgentMulder 와 같은 것을 사용해야 합니다. 이 단락에서 설명하는 다른 이점은 IoC가 아닌 DI와 관련이 있습니다. 또한 new 를 사용 하지 않지만 매개 변수에 대한 인터페이스 대신 콘크리트 유형을 사용 하면 클래스가 여전히 연결됩니다 .
Den

1
전체적으로 IoC는 결함이없는 것으로 페인트합니다. 예를 들어 컴파일 타임 대신 런타임에 오류 클래스를 푸시하는 것과 같은 큰 단점은 없습니다.
Den

2

단일 책임 원칙에 따라 모든 수업은 단일 책임만을 가져야합니다. 클래스의 새 인스턴스를 만드는 것은 또 다른 책임이므로 이러한 종류의 코드를 하나 이상의 클래스에 캡슐화해야합니다. 팩토리, 빌더, DI 컨테이너 등과 같은 모든 생성 패턴을 사용하여이를 수행 할 수 있습니다.

제어 역전 및 의존성 역전과 같은 다른 원칙이 있습니다. 이러한 맥락에서 그것들은 의존성의 인스턴스화와 관련이있다. 그들은 높은 수준의 클래스는 그들이 사용하는 낮은 수준의 클래스 (종속성)와 분리되어야한다고 말합니다. 인터페이스를 만들어서 분리 할 수 ​​있습니다. 따라서 하위 수준 클래스는 특정 인터페이스를 구현해야하며 상위 수준 클래스는 이러한 인터페이스를 구현하는 클래스 인스턴스를 사용해야합니다. (참고 : REST 균일 한 인터페이스 제약 조건은 시스템 수준에서 동일한 접근 방식을 적용합니다.)

예를 들어 이러한 원칙의 조합 (낮은 품질의 코드에 대해서는 죄송하지만 C # 대신 임시 언어를 사용했습니다.

  1. SRP 없음, IoC 없음

    class SomeHighLevelService
    {
        public doFooBar(){
            Crap crap = doFoo();
            doBar(crap);
        }
    
        public Crap doFoo(){
            //...
            return crap;
        }
    
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  2. SRP에 더 가깝고 IoC가 없음

    class SomeHighLevelService
    {
        public SomeHighLevelService(){
            Foo foo = new Foo();
            Bar bar = new Bar();
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  3. 예 SRP, 아니요 IoC

    class HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new SomeHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new Foo();
        }
    
        private Bar getBar(){
            return new Bar();
        }
    }
    
    class SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    HighLevelServiceProvider provider = new HighLevelServiceProvider();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();
  4. 예 SRP, 예 IoC

    interface HighLevelServiceProvider {
        SomeHighLevelService getSomeHighLevelService();
    }
    
    interface SomeHighLevelService {
        doFooBar();
    }
    
    interface Foo {
        Crap doFoo();
    }
    
    interface Bar {
        doBar(Crap crap);
    }
    
    
    class ConcreteHighLevelServiceContainer implements HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new ConcreteHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new ConcreteFoo();
        }
    
        private Bar getBar(){
            return new ConcreteBar();
        }
    }
    
    class ConcreteHighLevelService implements SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class ConcreteFoo implements Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class ConcreteBar implements Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    
    HighLevelServiceProvider provider = new ConcreteHighLevelServiceContainer();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();

그래서 우리는 모든 구체적인 구현을 동일한 인터페이스 ofc를 구현하는 다른 구현으로 대체 할 수있는 코드를 갖게되었습니다. 참여하는 클래스가 서로 분리되어 있기 때문에 인터페이스 만 알기 때문에 좋습니다. 인스턴스화 코드를 재사용 할 수 있다는 또 다른 장점.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.