2012-09-03 2 views
2

나는 데이터베이스에 연결하고 제공된 표준을 기반으로 특정 제품을 선택하고이 제품을 사용하여 처리하는 간단한 콘솔 응용 프로그램을 작성했습니다. 나는이 클래스의 인스턴스에 명령 줄 인수를 저장하고있다 :생성자 삽입 및 주입되는 생성자의 생성

public class Arguments 
{ 
    public string ConnectionString { get; set; } 
    public int ProductId { get; set; } 
    public string ProductName { get; set; } 
} 

어떤 시점에서, 나는 데이터베이스에서 제품을 가져해야합니다. 그 내용은 다음 저장소를 사용하고 있습니다 :

다음
public interface IProductRepository 
{ 
    Product GetById(int productId, string connectionString); 
    Product GetByName(string productName, string connectionString); 
} 

, 나는 그것을 사용하는 클래스, 예에 저장소의 구현 주입 :

public class ProductProcessor 
{ 
    private readonly IProductRepository productRepository; 

    public ProductProcessor(IProductRepository productRepository) 
    { 
     this.productRepository = productRepository; 
    } 

    public void Process(Arguments arguments) 
    { 
     Product productToProcess; 

     if (!string.IsNullOrEmpty(arguments.ProductName)) 
     { 
     productToProcess = productRepository.GetByName(arguments.ProductName, arguments.ConnectionString); 
     } 
     else 
     { 
     productToProcess = productRepository.GetById(arguments.ProductId, arguments.ConnectionString); 
     } 

     // .... 
    } 
} 

이 작동하는지,하지만 난 돈 '무엇을 디자인에 관해서는 IProductRepository의 모든 메소드가 connectionString 인수를가집니다. 반군 의존성 주입 없었다 경우에, 나는 아마 다음과 같이 그것을 다시 것입니다 : 이것은 간단하고 사용하기 쉬운 인터페이스를 가지고 나를 수

public void Process(Arguments arguments) 
{ 
    Product productToProcess; 

    ProductRepository productRepository = new ProductRepository(arguments.ConnectionString); 

    if (!string.IsNullOrEmpty(arguments.ProductName)) 
    { 
     productToProcess = productRepository.GetByName(arguments.ProductName); 
    } 
    else 
    { 
     productToProcess = productRepository.GetById(arguments.ProductId); 
    } 

    // .... 
} 

. 물론 ProductRepository에는 매개 변수없는 생성자가 없으므로 DI 컨테이너와 함께 사용하기가 어렵습니다. 이상적으로는 두 세계에서 가장 좋은 점, 즉 ProductRepository을 생성자의 연결 문자열로 초기화하고 해당 메서드에서 연결 문자열을 제거하고 싶습니다. 이것을 달성하는 가장 좋은 방법은 무엇입니까?

어떤 접근 방식은 내가 이미 고려했습니다

  • 는 기본적으로 생성자의 역할을 할 것 IProductRepository에 방법 Initialize(string connectionString)를 추가합니다. 명백한 단점은 GetById 또는 GetByName 메서드에서 아무 것도 수행하기 전에 Initialize이 호출되었는지 여부를 확인해야한다는 것입니다.
  • ProductRepository을 인스턴스화하는 대신 생성자 삽입을 사용하고 Service Locator 패턴을 사용하지 마십시오. 나는 Service Locator를 많이 좋아하지 않지만 이것은 아마도 유일한 해결책 일 것입니다.

더 좋은 대안이 있습니까?

편집 : 나는 좀 더 문맥을 게시 한 것을보고 답변에서. DI 컨테이너로 Ninject를 사용하고 있습니다. 내 Program.cs에서 Main 방법에서, 나는 컨테이너에 모든 종속성을 등록하고 응용 프로그램에 대한 진입 점 역할을하는 클래스의 인스턴스 :

public class MainClass 
{ 
    private readonly IArgumentsParser argumentsParser; 
    private readonly IProductProcessor productProcessor;   

    public MainClass(IArgumentsParser parser, IProductProcessor processor) 
    { 
     argumentsParser = parser; 
     productProcessor = processor; 
    } 

    public void Start(string[] args) 
    { 
     Arguments parsedArguments = argumentsParser.Parse(args); 
     productProcessor.Process(parsedArguments); 
    } 
} 
:

public static void Main(string[] args) 
{ 
    StandardKernel kernel = new StandardKernel(); 
    kernel.Bind<IArgumentsParser>().To<IArgumentsParser>(); 
    kernel.Bind<IProductProcessor>().To<ProductProcessor>(); 
    kernel.Bind<IProductRepository>().To<ProductRepository>(); 

    MainClass mainClass = kernel.Get<MainClass>(); 
    mainClass.Start(args); 
} 

MainClass은 다음과 같습니다를

이렇게하면 Ninject에 의존하고 전체 그래프를 한 곳에서만 만들 수 있습니다 (Main 메서드). 나머지 응용 프로그램에서는 DI 및 컨테이너에 대해 아무것도 모릅니다.

가능하면이 방법을 사용하고 싶습니다.

답변

4

나는 현재의 인터페이스 디자인이 새는 추상화 점에 동의, 그래서이 대신처럼 정의 할 수 있습니다 :

public class ProductProcessor 
{ 
    private readonly IProductRepositoryFactory productRepositoryFactory; 

    public ProductProcessor(IProductRepositoryFactory productRepositoryFactory) 
    { 
     this.productRepositoryFactory = productRepositoryFactory; 
    } 

    public void Process(Arguments arguments) 
    { 
     Product productToProcess; 

     var productRepository = 
      this.productRepositoryFactory.Create(arguments.ConnectionString); 
     if (!string.IsNullOrEmpty(arguments.ProductName)) 
     { 
      productToProcess = productRepository.GetByName(arguments.ProductName); 
     } 
     else 
     { 
      productToProcess = productRepository.GetById(arguments.ProductId); 
     } 

     // .... 
    } 
} 
:

public interface IProductRepository 
{ 
    Product GetById(int productId); 
    Product GetByName(string productName); 
} 

What you need then is an Abstract Factory that can create an instance of IProductRepository for you.

그래서 ProductProcessor는 다음과 같이 수

+0

연결 문자열 누출 추상화도 아닌가요? 너는 너의 책에서 이것을 진술했다. 아마도 이런 경우 (사용자 입력으로 연결 문자열)의 경우 명확하지 않습니다. – mnn

+0

예, 당신은 그것을 주장 할 수 있습니다. 그러나 방금 모든 것을 다 망치지 않기 위해 그 부분에 OP의 객체 모델을 사용했습니다. :) –

0

편집

당신이 언급 한 문제를 해결하는 방법은 다음과 같은 답변을 참조하십시오.

그러나 Windsor 또는 nHibernate와 같은 기존 IOC 패키지를 사용하는 것이 좋습니다. 자세한 내용은 https://stackoverflow.com/questions/2515124/whats-the-simplest-ioc-container-for-c을 참조하십시오.

최종 편집

왜 IProductRepository에 속성으로 ConnectionString을 추가하지.

public interface IProductRepository 
{ 
    string ConnectionString { get; set; } 
    Product GetById(int productId); 
    Product GetByName(string productName); 
} 

그리고 프로세서가된다 :

그래서 인터페이스는

public void Process(Arguments arguments) 
{ 
Product productToProcess; 

var productRepository = new ProductRepository 
     { ConnectionString = arguments.ConnectionString}; 

if (!string.IsNullOrEmpty(arguments.ProductName)) 
    productToProcess = productRepository.GetByName(arguments.ProductName); 
else 
    productToProcess = productRepository.GetById(arguments.ProductId); 

// .... 
} 
+0

속성을 사용하는 것은 정확히'Initialize' 메서드를 사용하는 것과 같습니다 (필자의 질문에 언급했습니다). 가능하다면 질문에 언급 된 이유 때문에 나는 그것을 피하고 싶습니다. –

3

난 당신이 전혀 명령 줄 인수를 모델링해야하는 이유 확실하지 않다? 각 유형에 대한 종속성을 최소화해야합니다. 즉, 제품 저장소는 연결 문자열을 생성자 매개 변수로 사용해야하며 (필수 종속성이므로) 제품 프로세서와 제품 이름을 사용해야합니다 (동적 쿼리를 수행하는 가장 좋은 방법 인 경우).

따라서 제품 저장소가 싱글 톤이라고 가정하면 등록을 수행 할 때 (연결 문자열 전달시) 새로 작성한 다음이를 IoC 컨테이너에 추상화에 등록하십시오.

그러면 제품 프로세서 (제품 ID와 제품 이름이 전달됨)를 새로 고치고 이것을 추상화에 대해 싱글 톤으로 등록 할 수 있습니다. 그런 다음 생성자 삽입을 사용하여 제품 프로세서를 필요로하는 모든 유형으로 전달할 수 있습니다.

+0

제발, 내가 만든 편집을 참조하십시오. 나는 원래의 질문에이 정보를 제공하지 않았기 때문에 사과드립니다. 문제는 컨테이너에 종속성을 등록하는 지점에서 연결 문자열의 값을 아직 알지 못한다는 것입니다. 제품 ID 및 이름에도 동일하게 적용됩니다. –

+0

연결 문자열, 제품 ID 및 제품 이름이 명령 줄 인수에서 오지 않았습니까? 나는. Main 메서드에 'args'매개 변수가 있습니까? 컨테이너에 유형을 등록하는 시점은 무엇입니까? Main 메서드에서 인스턴스화 한 인스턴스를 사용하도록 등록을 변경하면됩니다. – devdigital

2

물론, 지금 ProductRepository는 매개 변수가 생성자가없는 DI 컨테이너로 사용하기는 어렵다.

반대로 대부분의 DI 컨테이너를 사용하면 매개 변수화 된 생성자를 사용할 수 있습니다. 실제로 Dependency Injection을 수행 할 때 constructor injection은 권장되지 않은 생성자를 갖게됨을 의미하는 권고 된 접근 방식입니다. 기본 유형 (예 : 문자열 종속성)을 사용하는 생성자를 갖는 것은 일부 컨테이너가 자동 배선을 수행 할 수 없음을 의미 할 수 있습니다. 자동 배선은 컨테이너가 무엇을 주입해야 하는지를 알 수 있음을 의미합니다. 그러나 제품 저장소를 사용하면 컨테이너에 초기화 된 인스턴스를 제공하거나 (단일 인스턴스가 필요한 경우) 또는 팩토리 대리자를 제공하여이 문제를 쉽게 해결할 수 있습니다 (각 호출에서 새 인스턴스가 필요한 경우). 그것은 당신이 사용하는 프레임 워크에 의존하지만, 다음과 같습니다

container.RegisterSingle(new SqlProductFactory("constring")); 

당신이 SqlProductFactory의 생성자에서 연결 문자열을 공급하면 (방법 주입을 사용)에 전달할 필요가 없습니다에 팩토리, 그리고 귀하의 Arguments 클래스에이 연결 문자열이 필요하지 않습니다.

+0

내가 작성한 편집을 확인해 주시겠습니까? 나는 Ninject를 사용하고 있고 편집하는 방법은 내가하고있는 정확한 방법이다. 귀하의 대답은 그 맥락에서 어떻게 적용됩니까? –

2

당신이 할 수있는 것은 개체를 개체보기에서 분리하는 것입니다. DI 컨테이너는 시작시 등록한 인스턴스를 검색합니다. 이 때 연결 문자열을 생성자 인수로 저장소에 전달할 수 있습니다.

제품 코드는 다음과 같습니다.

public class ProductRepository : IProductRepority 
{ 
    private readonly string connString; 
    public ProductRepository(string conn) 
    { 
     connString = conn; 
    } 
} 

필요하면 연결 문자열을 다른 유형으로도 감쌀 수 있습니다. 중요한 점은 DI가 시작할 때 유형 그래프에서 수행 된 바인딩을 기반으로 필요한 인스턴스를 주입한다는 것입니다. 등록을 기반으로 args에서 연결 문자열을 간단히 추출하여 ProductRepository 등록을 통해 전달할 수 있습니다.

+0

설정 파일이 없습니다 - 연결 문자열은'Arguments' 인스턴스에 저장됩니다. 나는'IDbConnection' 의존성을 추가 할 수는 있지만 문제가'IProductRepository'에서'IDbConnection'으로 옮겨 질 것이라고 생각합니다. –

+0

연결 문자열이 각 인수마다 다름을 의미합니까? 그렇지 않은 경우 인수를 통해 전달할 필요가 없습니다. –