의존성 주입 생성자 광기를 피하는 방법?


300

내 생성자가 다음과 같이 보이기 시작했습니다.

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

매개 변수 목록이 계속 증가합니다. "컨테이너"는 의존성 주입 컨테이너이므로 왜 이렇게 할 수 없습니까?

public MyClass(Container con)

모든 수업에 대해? 단점은 무엇입니까? 이렇게하면 영광스러운 정적을 사용하고있는 것 같습니다. IoC 및 Dependency Injection 광기에 대한 의견을 공유하십시오.


64
왜 컨테이너를 전달합니까? 난 당신이 IOC 오해 것 같아요
폴 Creasey

33
생성자가 더 많은 매개 변수를 요구하는 경우 해당 클래스에서 너무 많은 작업을 수행하고있을 수 있습니다.
Austin Salonen

38
그것은 생성자 주입을 수행하는 방법이 아닙니다. 객체는 IoC 컨테이너에 대해 전혀 모릅니다.
duffymo

빈 생성자를 만들면 DI를 직접 호출하여 필요한 것을 요구할 수 있습니다. 그러면 생성자 madnes가 제거되지만 DI 시스템을 개발 중반으로 변경하는 경우 DI 인터페이스를 사용해야합니다. 솔직히 .. DI가 생성자에 주입하기 위해 수행하는 작업이지만 아무도이 방법으로 다시 작업하지 않습니다. doh
Piotr Kula

답변:


409

컨테이너를 서비스 로케이터로 사용하면 다소 영광스러운 정적 팩토리입니다. 여러 가지 이유로 나는 이것을 안티 패턴이라고 생각합니다 .

생성자 주입의 놀라운 이점 중 하나는 단일 책임 원칙을 위반하는 것이 눈에 띄게 한다는 것 입니다.

그런 일이 발생하면 Facade Services리팩토링해야합니다 . 요컨대, 현재 필요한 세밀한 종속성의 일부 또는 전부 간의 상호 작용을 숨기는 새롭고 더 세분화 된 인터페이스를 만듭니다 .


8
리팩토링 노력을 단일 개념으로 수량화하기위한 +1; 굉장 :)
라이언 Emerle

46
진짜? 이러한 매개 변수를 다른 클래스로 이동하기위한 간접 지시를 작성했지만 여전히 있습니다. 그것들을 다루기가 더 복잡합니다.
믿을 수없는

23
@irreputable : 모든 의존성을 Aggregate Service로 옮기는 퇴화적인 경우 에는 이점이없는 또 다른 간접적 인 수준이라는 것에 동의합니다. 그래서 제가 선택한 단어는 약간 벗어났습니다. 그러나 요점은 세부적인 종속성 중 일부만 집계 서비스로 이동한다는 것 입니다. 이는 새 집계 서비스와 남은 종속성에 대한 종속성 순열 수를 제한합니다. 이것은 둘 다 다루기가 더 간단합니다.
Mark Seemann

92
가장 좋은 설명 : "생성자 주입의 놀라운 이점 중 하나는 단일 책임 원칙을 위반하는 것이 명백하게 드러난다는 것입니다."
Igor Popov 2016 년

2
@DonBox이 경우 재귀를 중지하기 위해 null 개체 구현을 작성할 수 있습니다. 필요한 것은 아니지만 요점은 생성자 주입이주기를 막지 못한다는 것입니다.
Mark Seemann

66

클래스 생성자가 IOC 컨테이너 기간에 대한 참조를 가져야한다고 생각하지 않습니다. 이것은 클래스와 컨테이너 사이의 불필요한 종속성을 나타냅니다 (IOC가 피하려고하는 종속성 유형입니다!).


+1 IoC 컨테이너에 따라 나중에 다른 모든 클래스의 코드를 변경하지 않고도 컨테이너를 변경하기가 어렵습니다.
Tseng

1
생성자에 인터페이스 매개 변수없이 IOC를 어떻게 구현 하시겠습니까? 내가 당신의 게시물을 잘못 읽고 있습니까?
J Hunt

@J 헌트 나는 당신의 의견을 이해하지 못합니다. 나에게 인터페이스 매개 변수는 종속성에 대한 인터페이스 인 매개 변수, 즉 종속성 주입 컨테이너가 초기화되는 경우를 의미합니다 MyClass myClass = new MyClass(IDependency1 interface1, IDependency2 interface2)(인터페이스 매개 변수). 이것은 @derivation의 게시물과 관련이 없으며, 의존성 주입 컨테이너가 객체에 자신을 주입해서는 안된다고 해석하는 것으로 해석됩니다.MyClass myClass = new MyClass(this)
John Doe

25

매개 변수를 전달하기가 어렵다는 것은 문제가 아닙니다. 문제는 당신의 수업이 너무 많은 일을하고 있고, 더 세분화되어야한다는 것입니다.

의존성 주입은 클래스가 너무 커질 때, 특히 모든 의존성에 통과하는 고통이 커지기 때문에 조기 경고 역할을 할 수 있습니다.


43
내가 틀렸다면 나를 수정하십시오. 그러나 어느 시점에서 '모든 것을 함께 붙잡아 야'하므로 몇 가지 의존성을 가져야합니다. 예를 들어보기 계층에서 템플릿 및 데이터를 작성할 때 다양한 종속성 (예 : '서비스')의 모든 데이터를 가져온 다음이 데이터를 템플릿 및 화면에 넣어야합니다. 내 웹 페이지에 10 개의 서로 다른 정보 블록이있는 경우 해당 데이터를 제공하려면 10 개의 다른 클래스가 필요합니다. 그래서 View / Template 클래스에 10 개의 종속성이 필요합니까?
Andrew

4

생성자 기반 종속성 인젝션과 모든 종속성을 전달하는 것이 얼마나 복잡한 지에 대해 비슷한 질문을 받았습니다.

필자가 과거에 사용한 접근법 중 하나는 서비스 계층을 사용하여 애플리케이션 파사드 패턴을 사용하는 것입니다. 이것은 거친 API를 가질 것입니다. 이 서비스가 저장소에 의존하는 경우 개인 특성의 세터 주입을 사용합니다. 이를 위해서는 추상 팩토리를 작성하고 저장소를 작성하는 로직을 팩토리로 이동해야합니다.

설명과 함께 자세한 코드는 여기에서 찾을 수 있습니다

복잡한 서비스 계층의 IoC 모범 사례


3

나는이 글 전체를 두 번 읽었고, 사람들은 질문에 의한 것이 아니라 그들이 아는 것에 의해 응답한다고 생각합니다.

JP의 원래 질문은 리졸버와 많은 클래스를 전송하여 객체를 생성하는 것처럼 보이지만 이러한 클래스 / 객체는 자체적으로 서비스이며 주입에 적합하다고 가정합니다. 그들이 아닌 경우 어떻게?

JP, 당신이 활용 DI를 찾는 경우 상황에 맞는 데이터를 주입 혼합의 영광을 원하는, 이러한 패턴 (또는 가정 "안티 패턴")의 아무도 특별히 주소가. 실제로 그러한 노력으로 당신을 지원할 패키지를 사용하는 것으로 요약됩니다.

Container.GetSevice<MyClass>(someObject1, someObject2)

...이 형식은 거의 지원되지 않습니다. 구현과 관련된 비참한 성능에 추가 된 이러한 지원의 프로그래밍 어려움은 오픈 소스 개발자에게는 매력적이지 않다고 생각합니다.

그러나 MyClass에 대한 팩토리를 생성하고 등록 할 수 있어야하고 해당 팩토리는 전달을 위해 "서비스"로 푸시되지 않은 데이터 / 입력을 수신 할 수 있어야하기 때문에 수행되어야합니다. 데이터. "반 패턴 (anti-pattern)"이 부정적인 결과에 관한 것이라면, 데이터 / 모델을 전달하기위한 인공적인 서비스 유형의 존재를 강요하는 것은 확실히 부정적입니다 (클래스를 컨테이너로 묶는 것에 대한 느낌과 동일합니다. 동일한 본능이 적용됩니다).

비록 조금보기에 좋지 않더라도 도움이 될만한 프레임 워크가 있습니다. 예를 들어, Ninject :

생성자에서 추가 매개 변수와 함께 Ninject를 사용하여 인스턴스 만들기

그것은 .NET 용이며 인기가 있으며 여전히 깨끗한 곳은 아니지만, 당신이 선택한 언어가 무엇인지 확신합니다.


3

컨테이너를 주입하는 것은 결국 후회할 수있는 지름길입니다.

과다 주입은 문제가되지 않으며, 일반적으로 다른 구조적 결함의 증상이며, 특히 우려의 분리입니다. 이것은 하나의 문제는 아니지만 많은 출처를 가질 수 있으며 이것을 고치기 어렵게 만드는 것은 때로는 동시에 동시에 모든 문제를 해결해야한다는 것입니다 (스파게티 풀림을 생각할 때).

다음은 찾아야 할 것들의 불완전한 목록입니다.

불완전한 도메인 디자인 (Aggregate root 's…. etc)

문제 분리 불량 (서비스 구성, 명령, 쿼리) CQRS 및 이벤트 소싱을 참조하십시오.

또는 매퍼 (주의 : 이로 인해 문제가 발생할 수 있음)

모델 및 기타 DTO보기 (다시 재사용하지 말고 최소로 유지하십시오 !!!!)


2

문제 :

1) 매개 변수 목록이 계속 증가하는 생성자.

2) 클래스가 상속되면 (예 :) RepositoryBase생성자 서명을 변경하면 파생 클래스가 변경됩니다.

해결책 1

패스 IoC Container생성자

  • 더 이상 매개 변수 목록을 증가시키지 않습니다
  • 생성자의 서명이 간단 해집니다

왜 안돼?

  • 클래스를 IoC 컨테이너에 단단히 연결합니다. (1. 다른 IoC 컨테이너를 사용하는 다른 프로젝트에서 해당 클래스를 사용하려는 경우 문제가 발생합니다. 2. IoC 컨테이너를 변경하기로 결정 함)
  • 수업을 덜 설명 적으로 만듭니다. (실제로 클래스 생성자를보고 기능에 필요한 것을 말할 수는 없습니다.)
  • 클래스는 잠재적으로 모든 서비스에 액세스 할 수 있습니다.

해결책 2

모든 서비스를 그룹화하여 생성자에게 전달하는 클래스를 만듭니다.

 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

파생 클래스

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

  • 클래스에 새로운 종속성을 추가해도 파생 클래스에는 영향을 미치지 않습니다
  • 클래스는 IoC 컨테이너와 무관합니다
  • 클래스는 (종속성 측면에서) 설명 적입니다. 규칙에 따라 어떤 클래스가 A의존 하는지 알고 싶다면 해당 정보가 누적됩니다.A.Dependency
  • 생성자 서명이 간단 해짐

왜 안돼?

  • 추가 수업을 만들어야합니다
  • 서비스 등록이 복잡 해짐 (매번 등록해야 함 X.Dependency)
  • 통과와 개념적으로 동일 IoC Container
  • ..

솔루션 2는 단지 원시 일이지만 그것에 대한 확실한 주장이 있으면 설명적인 의견을 부탁드립니다.


1

이것이 내가 사용하는 접근법입니다

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

다음은 주입을 수행하고 값을 주입 한 후 생성자를 실행하는 방법입니다. 이것은 완전한 기능을 갖춘 프로그램입니다.

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

나는 현재 다음과 같이 작동하는 취미 프로젝트를 진행하고 있습니다 https://github.com/Jokine/ToolProject/tree/Core


-8

어떤 의존성 주입 프레임 워크를 사용하고 있습니까? 대신 세터 기반 주입을 사용해 보셨습니까?

생성자 기반 인젝션의 이점은 DI 프레임 워크를 사용하지 않는 Java 프로그래머에게는 자연스럽게 보입니다. 클래스를 초기화하려면 5 가지가 필요하며 생성자에 5 개의 인수가 있습니다. 단점은 당신이 주목 한 것입니다. 많은 의존성이있을 때 다루기가 어려워집니다.

Spring을 사용하면 setter를 사용하여 필요한 값을 전달할 수 있으며 @required 주석을 사용하여 주입되도록 할 수 있습니다. 단점은 초기화 코드를 생성자에서 다른 메소드로 옮기고 @PostConstruct로 표시하여 모든 종속성이 주입 된 후에 Spring을 호출해야한다는 것입니다. 다른 프레임 워크에 대해서는 확실하지 않지만 비슷한 프레임 워크를 사용한다고 가정합니다.

두 가지 방법 모두 작동하며 선호도는 중요합니다.


21
생성자 삽입의 이유는 종속성이 분명해지기 때문입니다. Java 개발자에게는 더 자연 스럽기 때문이 아닙니다.
L-Four

8
늦은 의견이지만이 답변은 나를 웃게했습니다 :)
Frederik Prijck

1
세터 기반 주입의 경우 +1 내 클래스에 서비스와 리포지토리가 정의되어 있으면 종속성이 분명합니다. 대규모 VB6 생성자를 작성하고 생성자에서 어리석은 코드를 할당 할 필요가 없습니다. 필요한 필드에 대한 종속성이 무엇인지는 분명합니다.
Piotr Kula

2018 년 기준으로 Spring은 공식적으로 적절한 기본값을 가진 의존성을 제외하고 setter injection을 사용하지 않을 것을 공식적으로 권장합니다. 마찬가지로 클래스에 대한 종속성이 필수 인 경우 생성자 삽입이 권장됩니다.
John Doe의
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.