IT박스

개자식 주사에 대한 대안이 있습니까?

itboxs 2020. 7. 20. 07:52
반응형

개자식 주사에 대한 대안이 있습니까? (기본 생성자를 통한 AKA 가난한 사람의 주입)


나는 보통 몇 가지 경우에 "자식 주입"을 사용하고 싶은 유혹을받습니다. "적절한"의존성 주입 생성자가있을 때 :

public class ThingMaker {
    ...
    public ThingMaker(IThingSource source){
        _source = source;
    }

그러나 공용 API (다른 개발 팀이 사용할 클래스) 로 사용하려는 클래스의 경우 가장 필요한 종속성을 가진 기본 "버스터 드"생성자를 작성하는 것보다 더 나은 옵션을 찾을 수 없습니다.

    public ThingMaker() : this(new DefaultThingSource()) {} 
    ...
}

여기서 명백한 단점은 이것이 DefaultThingSource에 대한 정적 종속성을 생성한다는 것입니다. 이상적으로는 그러한 의존성이 없으며 소비자는 항상 원하는 IThingSource를 주입합니다. 그러나 이것은 사용하기가 너무 어렵습니다. 소비자는 ThingMaker를 새로 만들고 물건을 만들기 위해 일하고 싶어합니다. 그런 다음 몇 달 후에 필요할 때 다른 것을 주입합니다. 내 의견으로는 몇 가지 옵션이 있습니다.

  1. 자식 생성자를 생략하십시오. ThingMaker 소비자가 IThingSource를 이해하고, ThingMaker가 IThingSource와 상호 작용하는 방식을 이해하고, 구체적인 클래스를 찾거나 작성한 다음 생성자 호출에서 인스턴스를 삽입하도록합니다.
  2. 놈 생성자를 생략하고 별도의 팩토리, 컨테이너 또는 다른 부트 스트랩 클래스 / 방법을 제공하십시오. 어떻게 든 소비자는 자신의 IThingSource를 작성할 필요가 없다는 것을 소비자에게 이해 시키게됩니다. ThingMaker 소비자가 공장이나 부트 스트 래퍼를 찾아 이해하도록 강요합니다.
  3. 자식이 생성자를 유지하여 소비자가 개체를 "새로 만들어"실행할 수있게하고 DefaultThingSource의 선택적 정적 종속성에 대처할 수 있습니다.

소년, # 3 확실히 매력적입니다. 또 다른 더 나은 옵션이 있습니까? # 1 또는 # 2는 그만한 가치가없는 것 같습니다.


내가 이해하는 한이 질문은 느슨하게 결합 된 API를 적절한 기본값으로 노출시키는 방법과 관련이 있습니다. 이 경우 로컬 기본값 이 양호 할 수 있으며이 경우 종속성은 선택 사항으로 간주 될 수 있습니다. 선택적 종속성 을 처리하는 한 가지 방법 생성자 삽입 대신 속성 삽입 을 사용하는 것입니다 . 실제로 이것은 속성 삽입에 대한 포스터 시나리오의 일종입니다.

그러나 Bastard Injection의 실제 위험은 기본값이 Foreign Default 인 경우입니다. 이는 기본 생성자가 기본값을 구현하는 어셈블리에 바람직하지 않은 커플 링을 드래그한다는 것을 의미하기 때문입니다. 그러나이 질문을 이해함에 따라 의도 한 불이행은 동일한 어셈블리에서 시작되며,이 경우 특별한 위험이 없습니다.

어쨌든 이전 답변 중 하나에 설명 된 것처럼 Facade를 고려할 수도 있습니다. Dependency Inject (DI) "friendly"library

BTW, 여기에 사용 된 용어는 내 책 의 패턴 언어를 기반으로합니다 .


내 절충점은 @BrokenGlass의 스핀입니다.

1) 단독 생성자는 매개 변수화 된 생성자입니다

2) 공장 방법을 사용하여 ThingMaker를 생성하고 해당 기본 소스를 전달하십시오.

public class ThingMaker {
  public ThingMaker(IThingSource source){
    _source = source;
  }

  public static ThingMaker CreateDefault() {
    return new ThingMaker(new DefaultThingSource());
  }
}

분명히 이것은 당신의 의존성을 제거하지는 않지만,이 객체에는 호출자가 그들이 관심을 가질 때 깊이 잠수 할 수있는 의존성이 있음을 분명히합니다. 이해하는 데 도움이되는 경우 (CreateThingMakerWithDefaultThingSource)를 좋아하면 팩토리 메소드를보다 명확하게 만들 수 있습니다. IThingSource 팩토리 메소드를 대체하는 것이 선호됩니다. 구성을 계속 선호하기 때문입니다. DefaultThingSource가 더 이상 사용되지 않을 때 새 팩토리 메소드를 추가하고 DefaultThingSource를 사용하여 모든 코드를 찾아 업그레이드하도록 표시 할 수있는 명확한 방법이 있습니다.

당신은 당신의 질문에서 가능성을 다루었습니다. 클래스 자체의 편의 또는 일부 편의를위한 다른 클래스 클래스. 다른 매력없는 옵션은 리플렉션 기반이며 의존성을 훨씬 더 숨 깁니다.


하나 개의 대안은 가지고있다 팩토리 메소드 CreateThingSource() 당신의 ThingMaker당신에 대한 종속성을 생성 클래스를.

테스트를 위해 또는 다른 유형이 필요한 경우 원하는 구체적 유형을 리턴 IThingSource하기 위해 서브 클래스를 작성 ThingMaker하고 대체 CreateThingSource()해야합니다. 분명히이 접근법은 테스트에 의존성을 주입 할 수 있어야하지만 대부분의 다른 목적으로 다른 것이 필요하지 않은 경우에만 가치가 있습니다.IThingSource


# 3에 투표합니다. 당신은 당신의 삶과 다른 개발자의 삶을 더 쉽게 만들 것입니다.


Poor Man 's Dependency Injection으로도 알려진 "기본"종속성이 있어야하는 경우 종속성을 초기화하고 "연결"해야합니다.

두 생성자를 유지하지만 초기화를 위해 팩토리를 갖습니다.

public class ThingMaker
{
    private IThingSource _source;

    public ThingMaker(IThingSource source)
    {
        _source = source;
    }

    public ThingMaker() : this(ThingFactory.Current.CreateThingSource())
    {
    }
}

Now in the factory create the default instance and allow the method to be overrided:

public class ThingFactory
{
    public virtual IThingSource CreateThingSource()
    {
        return new DefaultThingSource();
    }
}

Update:

Why using two constructors: Two constructors clearly show how the class is intended to be used. The parameter-less constructor states: just create an instance and the class will perform all of it's responsibilities. Now the second constructor states that the class depends of IThingSource and provides a way of using an implementation different than the default one.

Why using a factory: 1- Discipline: Creating new instances shouldn't be part of the responsibilities of this class, a factory class is more appropriate. 2- DRY: Imagine that in the same API other classes also depend on IThingSource and do the same. Override once the factory method returning IThingSource and all the classes in your API automatically start using the new instance.

I don't see a problem in coupling ThingMaker to a default implementation of IThingSource as long as this implementation makes sense to the API as a whole and also you provide ways to override this dependency for testing and extension purposes.


You are unhappy with the OO impurity of this dependency, but you don't really say what trouble it ultimately causes.

  • Is ThingMaker using DefaultThingSource in any way that does not conform to IThingSource? No.
  • Could there come a time where you would be forced to retire the parameterless constructor? Since you are able to provide a default implementation at this time, unlikely.

I think the biggest problem here is the choice of name, not whether to use the technique.


The examples usually related to this style of injection are often extremely simplisitic: "in the default constructor for class B, call an overloaded constructor with new A() and be on your way!"

The reality is that dependencies are often extremely complex to construct. For example, what if B needs a non-class dependency like a database connection or application setting? You then tie class B to the System.Configuration namespace, increasing its complexity and coupling while lowering its coherence, all to encode details which could simply be externalized by omitting the default constructor.

This style of injection communicates to the reader that you have recognized the benefits of decoupled design but are unwilling to commit to it. We all know that when someone sees that juicy, easy, low-friction default constructor, they are going to call it no matter how rigid it makes their program from that point on. They can't understand the structure of their program without reading the source code for that default constructor, which isn't an option when you just distribute the assemblies. You can document the conventions of connection string name and app settings key, but at that point the code doesn't stand on its own and you put the onus on the developer to hunt down the right incantation.

Optimizing code so those who write it can get by without understanding what they are saying is a siren song, an anti-pattern that ultimately leads to more time lost in unraveling the magic than time saved in initial effort. Either decouple or don't; keeping a foot in each pattern diminishes the focus of both.


For what it is worth, all the standard code I've seen in Java does it like this:

public class ThingMaker  {
    private IThingSource  iThingSource;

    public ThingMaker()  {
        iThingSource = createIThingSource();
    }
    public virtual IThingSource createIThingSource()  {
        return new DefaultThingSource();
    }
}

Anybody who doesn't want a DefaultThingSource object can override createIThingSource. (If possible, the call to createIThingSource would be somewhere other than the constructor.) C# does not encourage overriding like Java does, and it might not be as obvious as it would be in Java that the users can and perhaps should provide their own IThingSource implementation. (Nor as obvious how to provide it.) My guess is that #3 is the way to go, but I thought I would mention this.


Just an idea - perhaps a bit more elegant but sadly doesn't get rid of the dependency:

  • remove the "bastard constructor"
  • in the standard constructor you make the source param default to null
  • then you check for source being null and if this is the case you assign it "new DefaultThingSource()" otherweise whatever the consumer injects

Have an internal factory (internal to your library) that maps the DefaultThingSource to IThingSource, which is called from the default constructor.

This allows you to "new up" the ThingMaker class without parameters or any knowledge of IThingSource and without a direct dependency on DefaultThingSource.


For truly public APIs, I generally handle this using a two-part approach:

  1. Create a helper within the API to allow an API consumer to register "default" interface implementations from the API with their IoC container of choice.
  2. If it is desirable to allow the API consumer to use the API without their own IoC container, host an optional container within the API that is populated the same "default" implementations.

The really tricky part here is deciding when to activate the container #2, and the best choice approach will depend heavily on your intended API consumers.


I support option #1, with one extension: make DefaultThingSource a public class. Your wording above implies that DefaultThingSource will be hidden from public consumers of the API, but as I understand your situation there's no reason not to expose the default. Furthermore, you can easily document the fact that outside of special circumstances, a new DefaultThingSource() can always be passed to the ThingMaker.

참고URL : https://stackoverflow.com/questions/6733667/is-there-an-alternative-to-bastard-injection-aka-poor-mans-injection-via-defa

반응형