[design-patterns] 의존성 주입이란 무엇입니까?

의존성 주입 에 대한 특정 질문 , 사용시기 및 프레임 워크와 같은 몇 가지 질문이 이미 게시되어 있습니다. 하나,

의존성 주입이란 무엇이며 언제 / 왜 사용해야합니까?



답변

의존성 주입 은 다른 객체프레임 워크 (종속성 인젝터)에 의존성을 전달하고 있습니다.

의존성 주입은 테스트를 더 쉽게 만듭니다. 주입은 constructor을 통해 수행 할 수 있습니다 .

SomeClass() 생성자는 다음과 같습니다.

public SomeClass() {
    myObject = Factory.getObject();
}

문제점 : myObject디스크 액세스 또는 네트워크 액세스와 같은 복잡한 작업 이 관련된 경우 장치 테스트를 수행 하기가 어렵 습니다 SomeClass(). 프로그래머는 모의해야 하며 팩토리 호출을 가로 챌myObject 수 있습니다 .

대체 솔루션 :

  • myObject생성자에 인수로 전달
public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

myObject 테스트를 쉽게 할 수 있도록 직접 전달할 수 있습니다.

  • 일반적인 대안 중 하나는 do-nothing 생성자를 정의하는 입니다. 의존성 주입은 세터를 통해 수행 할 수 있습니다. (h / t @MikeVella).
  • Martin Fowler 는 세 번째 대안 (h / t @MarcDix)을 문서화했으며, 여기서 클래스 는 프로그래머가 주입하고자하는 종속성에 대한 인터페이스명시 적으로 구현합니다 .

의존성 주입없이 단위 테스트에서 구성 요소를 분리하는 것이 더 어렵습니다.

2013 년에이 답변을 썼을 때 Google 테스팅 블로그 의 주요 주제였습니다 . 프로그래머가 항상 런타임 디자인 (예 : 서비스 로케이터 또는 유사한 패턴)에서 추가 유연성을 필요로하는 것은 아니기 때문에 나에게 가장 큰 장점으로 남아 있습니다. 프로그래머는 종종 테스트 중에 클래스를 분리해야합니다.


답변

지금까지 찾은 최고의 정의는 James Shore 가 정의한 것입니다 .

“의존성 주입”은 5 센트 개념의 25 달러 용어입니다. […] 의존성 주입은 객체에 인스턴스 변수를 제공하는 것을 의미합니다. […].

마틴 파울러의 글 도 유용 할 수 있습니다.

의존성 주입은 기본적으로 객체 자체를 구성하는 대신 객체가 필요로하는 객체 (종속성)를 제공합니다. 의존성을 조롱하거나 스텁 아웃 할 수 있기 때문에 테스트에 매우 유용한 기술입니다.

종속성은 생성자 주입 또는 세터 주입과 같은 많은 방법으로 객체에 주입 될 수 있습니다. 특별한 의존성 주입 프레임 워크 (예 : Spring)를 사용하여 그렇게 할 수도 있지만 반드시 필요한 것은 아닙니다. 의존성 주입을 위해 이러한 프레임 워크가 필요하지 않습니다. 명시 적으로 객체 (종속성)를 인스턴스화하고 전달하는 것은 프레임 워크에 의한 주입만큼이나 좋은 주입입니다.


답변

느슨한 커플 링 측면 에서이 재미있는 예를 찾았습니다 .

모든 응용 프로그램은 서로 협력하여 유용한 작업을 수행하는 많은 개체로 구성됩니다. 전통적으로 각 오브젝트는 협업하는 종속 오브젝트 (종속성)에 대한 자체 참조를 가져옵니다. 이것은 고도로 결합 된 클래스와 테스트하기 어려운 코드로 이어집니다.

예를 들어, Car객체를 고려하십시오 .

A Car는 바퀴, 엔진, 연료, 배터리 등에 달려 있습니다. 전통적으로 우리는 Car객체 의 정의와 함께 이러한 종속 객체의 브랜드를 정의 합니다.

의존성 주입이없는 경우 (DI) :

class Car{
  private Wheel wh = new NepaliRubberWheel();
  private Battery bt = new ExcideBattery();

  //The rest
}

여기서 Car개체 는 종속 개체를 만드는 역할을합니다.

Wheel초기 NepaliRubberWheel()펑크 후 종속 객체의 유형을 변경하려면 어떻게해야 합니까? 새로운 의존성 인 say를 사용하여 Car 객체를 다시 만들어야 ChineseRubberWheel()하지만 Car제조업체 만이 가능합니다.

그렇다면 Dependency Injection우리를 위해 무엇을 해야합니까 …?

의존성 주입을 사용 하는 경우, 컴파일 시간 (자동차 제조 시간)이 아닌 런타임에 객체에 종속성이 부여 됩니다. Wheel원하는 때마다 변경할 수 있습니다 . 여기서 dependency( wheel)는 Car런타임에 주입 될 수 있습니다 .

의존성 주입을 사용한 후 :

여기서는 런타임에 종속성 (휠 및 배터리)을 주입 하고 있습니다. 따라서 용어 : 의존성 주입.

class Car{
  private Wheel wh; // Inject an Instance of Wheel (dependency of car) at runtime
  private Battery bt; // Inject an Instance of Battery (dependency of car) at runtime
  Car(Wheel wh,Battery bt) {
      this.wh = wh;
      this.bt = bt;
  }
  //Or we can have setters
  void setWheel(Wheel wh) {
      this.wh = wh;
  }
}

출처 : 의존성 주입 이해


답변

의존성 주입은 객체를 내부적으로 구성하는 대신 다른 코드 조각으로부터 객체의 인스턴스를 수신하는 방식으로 객체를 설계하는 방법입니다. 즉, 코드를 변경하지 않고도 객체에 필요한 인터페이스를 구현하는 모든 객체를 대체 할 수있어 테스트가 간소화되고 디커플링이 향상됩니다.

예를 들어 다음과 같은 걸쇠를 고려하십시오.

public class PersonService {
  public void addManager( Person employee, Person newManager ) { ... }
  public void removeManager( Person employee, Person oldManager ) { ... }
  public Group getGroupByManager( Person manager ) { ... }
}

public class GroupMembershipService() {
  public void addPersonToGroup( Person person, Group group ) { ... }
  public void removePersonFromGroup( Person person, Group group ) { ... }
} 

이 예에서의 구현 PersonService::addManagerPersonService::removeManager의 인스턴스를해야 GroupMembershipService그 일을하기 위해. Dependency Injection이 없으면 기존의 생성 방식은 GroupMembershipService생성자에서 새로운 인스턴스를 생성하고 PersonService두 인스턴스 모두에서 해당 인스턴스 속성을 사용하는 것입니다. 그러나의 생성자 GroupMembershipService가 여러 항목을 필요로하거나 더 나쁜 경우에는에 대해 호출해야하는 초기화 “세터”가 GroupMembershipService있으며 코드가 빠르게 커지고 PersonService현재는 GroupMembershipService다른 것뿐만 아니라 GroupMembershipService에 달려 있습니다. 또한에 대한 링크 GroupMembershipService는 하드 코드되어 있으므로 PersonService“더미”할 수 없습니다.GroupMembershipService 테스트 목적으로 또는 응용 프로그램의 다른 부분에서 전략 패턴을 사용합니다.

Dependency Injection을 사용하면의 GroupMembershipService내부 를 인스턴스화하는 대신 생성자 PersonService에 전달 PersonService하거나 속성 (getter 및 setter)을 추가하여 로컬 인스턴스를 설정할 수 있습니다. 이것은 PersonService더 이상을 만드는 방법에 대해 걱정할 필요가 없으며 GroupMembershipService주어진 것을 수락하고 작동합니다. 이것은 또한의 서브 클래스입니다 아무 의미 GroupMembershipService, 또는 구현 GroupMembershipService인터페이스는에 “주입”할 수 있습니다를 PersonService하고,이 PersonService변화에 대해 알 필요가 없다.


답변

받아 들일만한 대답은 좋은 것입니다.하지만 DI는 코드에서 하드 코딩 된 상수를 피하는 고전과 매우 흡사합니다.

데이터베이스 이름과 같은 상수를 사용하면 코드 내부에서 구성 파일로 빠르게 이동하고 해당 값을 포함하는 변수를 필요한 위치로 전달합니다. 그렇게하는 이유는 이러한 상수가 일반적으로 나머지 코드보다 자주 변경되기 때문입니다. 예를 들어 테스트 데이터베이스에서 코드를 테스트하려는 경우.

DI는 객체 지향 프로그래밍 세계에서 이와 유사합니다. 상수 리터럴 대신 값이 전체 객체이지만 클래스 코드에서 코드를 생성하는 이유는 비슷합니다. 객체가이를 사용하는 코드보다 자주 변경됩니다. 그러한 변경이 필요한 중요한 경우는 테스트입니다.


답변

Car 클래스 와 Engine 클래스 를 사용하여 간단한 예제를 시도해 봅시다 . 모든 자동차는 적어도 지금은 어디든 갈 수있는 엔진이 필요합니다. 아래 코드는 의존성 주입없이 어떻게 보일 것입니다.

public class Car
{
    public Car()
    {
        GasEngine engine = new GasEngine();
        engine.Start();
    }
}

public class GasEngine
{
    public void Start()
    {
        Console.WriteLine("I use gas as my fuel!");
    }
}

Car 클래스를 인스턴스화하기 위해 다음 코드를 사용합니다.

Car car = new Car();

우리가 GasEngine에 밀접하게 결합 한이 코드의 문제는 그것을 ElectricityEngine으로 변경하기로 결정한 경우 Car 클래스를 다시 작성해야합니다. 그리고 응용 프로그램이 클수록 더 많은 문제와 두통이 발생하여 새로운 유형의 엔진을 추가하고 사용해야합니다.

다시 말해,이 접근 방식을 사용하면 고급 자동차 등급은 SOLID의 DIP (Dependency Inversion Principle)를 위반하는 하위 레벨 GasEngine 등급에 의존합니다. DIP는 구체적인 클래스가 아닌 추상화에 의존해야한다고 제안합니다. 이를 만족시키기 위해 IEngine 인터페이스를 소개하고 아래와 같이 코드를 다시 작성합니다 :

    public interface IEngine
    {
        void Start();
    }

    public class GasEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I use gas as my fuel!");
        }
    }

    public class ElectricityEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I am electrocar");
        }
    }

    public class Car
    {
        private readonly IEngine _engine;
        public Car(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Start();
        }
    }

이제 Car 클래스는 엔진의 특정 구현이 아니라 IEngine 인터페이스에만 의존합니다. 이제 유일한 트릭은 어떻게 Car 인스턴스를 생성하고 GasEngine 또는 ElectricityEngine과 같은 실제 콘크리트 엔진 클래스를 제공 하는가입니다. 그것이 의존성 주입 이 들어오는 곳 입니다.

   Car gasCar = new Car(new GasEngine());
   gasCar.Run();
   Car electroCar = new Car(new ElectricityEngine());
   electroCar.Run();

여기서 기본적으로 의존성 (Engine instance)을 Car 생성자에 주입 (전달)합니다. 이제 우리 클래스는 객체와 그 종속성 사이의 느슨한 결합을 가지고 있으며 Car 클래스를 변경하지 않고도 새로운 유형의 엔진을 쉽게 추가 할 수 있습니다.

클래스가 하드 코딩 된 종속성을 갖지 않기 때문에 클래스가 더 느슨하게 결합되어 있다는 Dependency Injection 의 주요 이점입니다 . 이것은 위에서 언급 한 종속성 반전 원리를 따릅니다. 클래스는 특정 구현을 참조하는 대신 클래스가 생성 될 때 제공되는 추상화 (일반적으로 인터페이스 )를 요청 합니다.

결국 의존성 주입 은 객체와 그 의존성 사이의 느슨한 결합을 달성하는 기술 일뿐입니다. 클래스가 작업을 수행하기 위해 필요한 종속성을 직접 인스턴스화하지 않고 생성자 주입을 통해 클래스에 종속성을 제공합니다 (대부분의 경우).

또한 많은 의존성이있는 경우 컨트롤의 IoC (Inversion of Control) 컨테이너를 사용하는 것이 좋습니다.이 인터페이스를 사용하면 모든 인터페이스에 대해 어떤 구체적인 구현에 어떤 인터페이스를 매핑해야하는지 알 수 있으며 구성 할 때 해당 종속성을 해결할 수 있습니다 우리의 목표. 예를 들어, IoC 컨테이너의 매핑에서 IEngine 종속성이 GasEngine 클래스에 매핑되어야한다고 지정할 수 있으며 IoC 컨테이너에 Car 클래스 의 인스턴스를 요청하면 GasEngine 종속성으로 Car 클래스 가 자동으로 생성됩니다. 통과했다.

업데이트 : 최근 Julie Lerman의 EF Core에 대한 코스를 시청했으며 DI에 대한 짧은 정의를 좋아했습니다.

의존성 주입은 응용 프로그램이 필요한 클래스에 객체를 즉시 주입 할 수 있도록하는 패턴으로, 해당 클래스가 해당 객체를 책임지지 않도록합니다. 코드를 느슨하게 결합 할 수 있으며 Entity Framework Core는이 동일한 서비스 시스템에 플러그인됩니다.


답변

낚시하러 가고 싶다고 상상해 봅시다.

  • 의존성 주입이 없으면 모든 것을 스스로 관리해야합니다. 보트를 찾고, 낚싯대를 구입하고, 미끼 등을 찾는 것이 필요합니다. 물론 가능하지만, 그것은 당신에게 많은 책임이 있습니다. 소프트웨어 용어로,이 모든 것을 검색해야합니다.

  • 의존성 주입을 사용하면 다른 사람이 모든 준비를 처리하고 필요한 장비를 사용할 수 있습니다. 보트, 낚싯대 및 미끼를 모두 사용할 수 있습니다.