주변 컨텍스트와 생성자 주입


9

데이터베이스의 ISessionContext, 로그의 경우 ILogManager 및 다른 서비스와의 통신에 사용되는 IService가 필요한 많은 핵심 클래스가 있습니다. 모든 핵심 클래스에서 사용하는이 클래스에 종속성 주입을 사용하고 싶습니다.

두 가지 가능한 구현이 있습니다. 세 클래스 모두에서 IAmbientContext를 허용하거나 모든 클래스에 세 클래스를 삽입하는 핵심 클래스입니다.

public interface ISessionContext 
{
    ...
}

public class MySessionContext: ISessionContext 
{
    ...
}

public interface ILogManager 
{

}

public class MyLogManager: ILogManager 
{
    ...
}

public interface IService 
{
    ...
}

public class MyService: IService
{
    ...
}

첫 번째 해결책 :

public class AmbientContext
{
    private ISessionContext sessionContext;
    private ILogManager logManager;
    private IService service;

    public AmbientContext(ISessionContext sessionContext, ILogManager logManager, IService service)
    {
        this.sessionContext = sessionContext;
        this.logManager = logManager;
        this.service = service;
    }
}


public class MyCoreClass(AmbientContext ambientContext)
{
    ...
}

두 번째 솔루션 (주변 컨텍스트없이)

public MyCoreClass(ISessionContext sessionContext, ILogManager logManager, IService service)
{
    ...
}

이 경우 Wich가 최고의 솔루션입니까?


" IService다른 서비스와의 통신에 사용되는 것은 무엇입니까 ?" IService다른 서비스에 대한 모호한 종속성을 나타내는 경우 서비스 로케이터처럼 들리며 존재해서는 안됩니다. 클래스는 소비자가 자신의 작업을 명시 적으로 설명하는 인터페이스에 의존해야합니다. 어떤 클래스도 서비스에 대한 액세스를 제공하기 위해 서비스가 필요하지 않습니다. 클래스에는 클래스에 필요한 특정 작업을 수행하는 종속성이 필요합니다.
Scott Hannen

답변:


4

여기서 "최고"는 너무 주관적입니다. 그러한 결정에 공통적 인 것처럼, 그것은 무언가를 달성하는 두 가지 똑같이 유효한 방법 사이의 균형입니다.

당신은 만들 경우 AmbientContext많은 클래스로, 당신은 잠재적으로 그들보다이 필요 그들 각각에 더 많은 정보를 제공하고 있습니다 (예를 들어, 클래스가 있음을 주사한다 Foo에만 사용할 수 ISessionContext있지만 대해 이야기하고 있습니다 ILogManagerISession도 참조).

매개 변수를 통해 각 매개 변수를 전달하면 각 클래스에 알아야 할 사항 만 알려줍니다. 그러나 매개 변수의 수가 빠르게 증가 할 수 있으며 컨텍스트 클래스를 통해 단순화 될 수있는 반복되는 많은 매개 변수를 가진 생성자와 메서드가 너무 많음을 알 수 있습니다.

따라서 두 가지의 균형을 잡고 상황에 맞는 것을 선택하는 경우입니다. 하나의 클래스와 세 개의 매개 변수 만 있다면 개인적으로을 귀찮게하지 않을 것입니다 AmbientContext. 나에게 티핑 포인트는 아마도 네 가지 매개 변수 일 것입니다. 그러나 그것은 순수한 의견입니다. 당신의 팁 포인트는 내 것과 다를 수 있으므로 자신에게 맞는 느낌으로 가십시오.


4

질문의 용어가 실제로 예제 코드와 일치하지 않습니다. 이것은 Ambient Context모든 클래스를 오염시켜 종속성의 인터페이스를 수용하지 않고 제어 반전이라는 아이디어를 유지하면서 가능한 한 쉽게 모든 모듈의 클래스에서 종속성을 얻는 데 사용되는 패턴입니다. 이러한 종속성은 일반적으로 해당 응용 프로그램의 교차 절단 문제에 대한 로깅, 보안, 세션 관리, 트랜잭션, 캐싱, 감사에 전념합니다. 생성자에 ILogging, 를 추가하는 것은 다소 성가신 ISecurityITimeProvider이며 대부분의 시간에 모든 클래스가 동시에 필요한 것은 아니므로 귀하의 필요를 이해합니다.

ISession인스턴스 수명 이 다른 경우 ILogger어떻게됩니까? ISession 인스턴스는 모든 요청과 ILogger에 한 번 작성되어야합니다. 따라서 컨테이너 자체가 아닌 하나의 객체로 이러한 모든 종속성을 관리하는 것은 수명 관리 및 현지화 와이 스레드에 설명 된 다른 모든 문제로 인해 올바른 선택으로 보이지 않습니다.

IAmbientContext질문에 모든 생성자를 오염하지 않는 문제가 해결되지 않습니다. 여전히 생성자 서명에 사용해야합니다. 이번에는 한 번만 사용하십시오.

따라서 가장 쉬운 방법은 생성자 주입 또는 다른 주입 메커니즘을 사용하여 교차 절단 종속성을 처리하는 것이 아니라 정적 호출을 사용하는 것 입니다. 실제로이 패턴은 프레임 워크 자체에 의해 구현되는 경우가 많습니다. 인터페이스 의 구현을 리턴하는 정적 특성 인 Thread.CurrentPrincipal 을 확인하십시오 IPrincipal. 또한 원하는 경우 구현을 변경할 수 있으므로 설정할 수 있으므로 연결되지 않습니다.

MyCore 이제 다음과 같이 보입니다.

public class MyCoreClass
{
    public void BusinessFeature(string data)
    {
        LoggerContext.Current.Log(data);

        _repository.SaveProcessedData();

        SessionContext.Current.SetData(data);
        ...etc
    }
}

이 패턴과 가능한 구현은이 기사 에서 Mark Seemann에 의해 자세히 설명되었습니다 . 사용하는 IoC 컨테이너 자체에 의존하는 구현이있을 수 있습니다.

당신은 피하려고 AmbientContext.Current.Logger, AmbientContext.Current.Session위에서 설명한 것과 같은 이유.

그러나 컨테이너 에이 기능이 있거나 AOP 인 경우 데코레이터, 동적 차단을 사용 하여이 문제를 해결할 수있는 다른 옵션이 있습니다. Ambient Context는 클라이언트가 의존성을 숨기므로 최후의 수단이되어야합니다. 인터페이스가 정말 모방 내 충동이 정적의 같은 의존성 사용하는 경우에 나는 아직도 주위 문맥을 사용 DateTime.Now또는 ConfigurationManager.AppSettings이 필요가 자주 발생합니다. 그러나 결국 생성자 주입은 이러한 유비쿼터스 의존성을 얻는 나쁜 아이디어가 아닐 수 있습니다.


3

나는 피할 것이다 AmbientContext.

첫째, 수업이 의존한다면 AmbientContext실제로 무엇을하는지 알 수 없습니다. 중첩 된 종속성 중 어떤 것이 사용되는지 파악하려면 해당 종속성의 사용을 살펴 봐야합니다. 또한 종속성의 수를 보거나 클래스 중 하나가 실제로 여러 개의 중첩 된 종속성을 나타낼 수 있으므로 클래스가 너무 많은지를 알 수 없습니다.

둘째, 여러 생성자 종속성을 피하기 위해 사용하는 경우이 접근법은 다른 개발자 (자신 포함)가 해당 환경 컨텍스트 클래스에 새 멤버를 추가하도록 권장합니다. 그런 다음 첫 번째 문제가 복잡해집니다.

셋째, AmbientContext모든 경우 또는 필요한 멤버를 모의할지 여부를 파악한 다음 해당 모의를 반환하는 모의를 설정해야합니다 (또는 두 배 테스트). 단위 테스트, 쓰기 및 유지 관리가 더 어렵습니다.

넷째, 응집력이없고 단일 책임 원칙을 위반하는 것입니다. 그렇기 때문에 "AmbientContext"와 같은 이름을 가진 이유는 관련이없는 많은 작업을 수행하며 그 기능에 따라 이름을 지정할 방법이 없기 때문입니다.

또한 인터페이스 멤버가 필요없는 클래스에 인터페이스 멤버를 도입하여 인터페이스 분리 원칙을 위반할 수 있습니다.


2

두 번째 (인터페이스 랩퍼 제외)

중간 클래스에서 캡슐화해야하는 다양한 서비스간에 상호 작용이 없으면 '인터페이스 인터페이스'를 도입 할 때 코드가 복잡해지고 유연성이 제한됩니다.

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