5

Tôi nhầm lẫn về việc triển khai Dependency Injection trong một ví dụ cụ thể.Làm việc với Nhà máy Trừu tượng được bơm qua DI container

Giả sử chúng tôi có lớp SomeClass có phụ thuộc kiểu IClassX.

public class SomeClass 
{ 
    public SomeClass(IClassX dependency){...} 
} 

Creation triển khai cụ thể của giao diện IClassX phụ thuộc vào thời gian chạy thông số N.

Với constructor nào đó, tôi không thể cấu hình DI container (Unity được sử dụng), vì tôi không biết những gì thực hiện IClassX sẽ được sử dụng trong thời gian chạy. Mark Seemann trong cuốn sách Dependency Injection In .Net gợi ý rằng chúng ta nên sử dụng Abstract Factory làm tham số tiêm.

Bây giờ chúng tôi có SomeAbstractFactory trả về việc triển khai IClassX dựa trên runtimeTimeater parametimeParam.

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(){ } 

    public IClassX GetStrategyFor(int runTimeParam) 
    { 
     switch(runTimeParam) 
     { 
      case 1: return new ClassX1(); 
      case 2: return new ClassX2(); 
       default : return new ClassDefault(); 
     } 
    } 
} 

SomeClass bây giờ chấp nhận ISomeAbstractFactory như một tham số tiêm:

public class SomeClass 
{ 
    public SomeClass(ISomeAbstractFactory someAbstractfactory){...} 
} 

Và đó là tốt. Chúng tôi chỉ có một thành phần gốc, nơi chúng tôi tạo ra đồ thị đối tượng. Chúng ta cấu hình container Unity để tiêm SomeAbstractFactory vào SomeClass.

Nhưng, chúng ta hãy giả sử rằng lớp ClassX1 và ClassX2 có phụ thuộc của mình:

public class ClassX1 : IClassX 
{ 
    public ClassX1(IClassA, IClassB) {...} 
} 

public class ClassX2 : IClassX 
{ 
    public ClassX2(IClassA, IClassC, IClassD) {...} 
} 

Làm thế nào để giải quyết IClassA, IClassB, IClassC và IClassD phụ thuộc?

1. tiêm qua constructor SomeAbstractFactory

Chúng tôi có thể tiêm triển khai cụ thể của IClassA, IClassB, IClassC và IClassD để SomeAbstractFactory như thế này:

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD) 
    {...} 
    ... 
} 

Unity chứa sẽ được sử dụng trong ban đầu thành phần gốc và sau đó sử dụng DI của người nghèo để trả về bê tông ClassX1 hoặc ClassX2 dựa trên tham số runTimeParam

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD){...} 

    public IClassX GetStrategyFor(int runTimeParam) 
    { 
     switch(runTimeParam) 
     { 
      case 1: return new ClassX1(classA, classB); 
      case 2: return new ClassX2(classA, classC, classD); 
       default : return new ClassDefault(); 
     } 
    } 
} 

Vấn đề với cách tiếp cận này:

  • SomeAbstractFactory biết về phụ thuộc mà don `t thực sự thuộc về nó.
  • đồ thị đối tượng sâu hơn sẽ đòi hỏi phải thay đổi cả constructor và lớp SomeAbstractFactory thực hiện
  • DI chứa sẽ không được sử dụng để giải quyết phụ thuộc, man`s nghèo DI phải được sử dụng

2. gọi Explicit để DI vùng chứa

Thay vì "làm mới" ClassX1 hoặc ClassX2, chúng tôi sẽ giải quyết chúng bằng cách sử dụng vùng chứa DI.

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(IUnityContainer container){...} 

    public IClassX GetStrategyFor(int runTimeParam) 
    { 
     switch(runTimeParam) 
     { 
      case 1: return container.Resolve<IClassX>("x1"); 
      case 2: return container.Resolve<IClassX>("x2"); 
       default : return container.Resolve<IClassX>("xdefault"); 
     } 
    } 
} 

Vấn đề với cách tiếp cận này:

  • DI container được thông qua vào SomeAbstractFactory
  • DI phương pháp Giải quyết không chỉ được sử dụng ở thư mục gốc tổng hợp (ServiceLocator chống mẫu)

Có cách tiếp cận nào phù hợp hơn không?

Trả lời

1

Ví dụ dưới đây cho thấy cách thực hiện điều này với Unity. This blog post giải thích nó tốt hơn một chút khi sử dụng Windsor. Khái niệm cơ bản là chính xác như nhau cho mỗi, chỉ cần thực hiện hơi khác nhau.

Tôi thà cho phép nhà máy trừu tượng của tôi truy cập vào vùng chứa. Tôi xem nhà máy trừu tượng như một cách để ngăn chặn sự phụ thuộc vào container - lớp của tôi chỉ phụ thuộc vào IFactory, do đó, nó chỉ là việc thực hiện các nhà máy sử dụng container. Castle Windsor đi một bước xa hơn - bạn xác định giao diện cho nhà máy nhưng Windsor cung cấp việc triển khai thực tế. Nhưng đó là một dấu hiệu tốt cho thấy cách tiếp cận tương tự hoạt động trong cả hai trường hợp và bạn không phải thay đổi giao diện của nhà máy.

Trong cách tiếp cận bên dưới, điều cần thiết là lớp tùy thuộc vào nhà máy chuyển một số đối số cho phép nhà máy xác định cá thể nào cần tạo. Nhà máy sẽ chuyển đổi nó thành một chuỗi và vùng chứa sẽ khớp với nó bằng một phiên bản có tên. Cách tiếp cận này làm việc với cả Unity và Windsor.

Làm theo cách này, lớp tùy thuộc vào IFactory không biết rằng nhà máy đang sử dụng giá trị chuỗi để tìm đúng loại. Trong ví dụ Windsor, một lớp chuyển đối tượng Address tới nhà máy và nhà máy sử dụng đối tượng đó để xác định trình xác thực địa chỉ nào sẽ sử dụng dựa trên quốc gia của địa chỉ. Không có lớp khác nhưng nhà máy "biết" cách chọn đúng loại. Điều đó có nghĩa là nếu bạn chuyển sang một vùng chứa khác, điều duy nhất bạn phải thay đổi là triển khai của IFactory. Không có gì phụ thuộc vào IFactory phải thay đổi.

Dưới đây là mẫu mã sử dụng Unity:

public interface IThingINeed 
{} 

public class ThingA : IThingINeed { } 
public class ThingB : IThingINeed { } 
public class ThingC : IThingINeed { } 

public interface IThingINeedFactory 
{ 
    IThingINeed Create(ThingTypes thingType); 
    void Release(IThingINeed created); 
} 

public class ThingINeedFactory : IThingINeedFactory 
{ 
    private readonly IUnityContainer _container; 

    public ThingINeedFactory(IUnityContainer container) 
    { 
     _container = container; 
    } 

    public IThingINeed Create(ThingTypes thingType) 
    { 
     string dependencyName = "Thing" + thingType; 
     if(_container.IsRegistered<IThingINeed>(dependencyName)) 
     { 
      return _container.Resolve<IThingINeed>(dependencyName); 
     } 
     return _container.Resolve<IThingINeed>(); 
    } 

    public void Release(IThingINeed created) 
    { 
     _container.Teardown(created); 
    } 
} 

public class NeedsThing 
{ 
    private readonly IThingINeedFactory _factory; 

    public NeedsThing(IThingINeedFactory factory) 
    { 
     _factory = factory; 
    } 

    public string PerformSomeFunction(ThingTypes valueThatDeterminesTypeOfThing) 
    { 
     var thingINeed = _factory.Create(valueThatDeterminesTypeOfThing); 
     try 
     { 
      //This is just for demonstration purposes. The method 
      //returns the name of the type created by the factory 
      //so you can tell that the factory worked.     
      return thingINeed.GetType().Name; 
     } 
     finally 
     { 
      _factory.Release(thingINeed); 
     } 
    } 
} 

public enum ThingTypes 
{ 
    A, B, C, D 
} 

public class ContainerConfiguration 
{ 
    public void Configure(IUnityContainer container) 
    { 
     container.RegisterType<IThingINeedFactory,ThingINeedFactory>(new InjectionConstructor(container)); 
     container.RegisterType<IThingINeed, ThingA>("ThingA"); 
     container.RegisterType<IThingINeed, ThingB>("ThingB"); 
     container.RegisterType<IThingINeed, ThingC>("ThingC"); 
     container.RegisterType<IThingINeed, ThingC>(); 
    } 
} 

Dưới đây là một số xét nghiệm đơn vị. Chúng cho thấy rằng nhà máy trả về đúng loại IThingINeed sau khi kiểm tra những gì đã được chuyển đến chức năng Create() của nó.

Trong trường hợp này (có thể hoặc không thể áp dụng được), tôi cũng chỉ định một loại làm mặc định. Nếu không có gì được đăng ký với vùng chứa khớp chính xác với yêu cầu thì nó có thể trả về giá trị mặc định đó. Mặc định đó cũng có thể là một cá thể rỗng không có hành vi. Nhưng tất cả lựa chọn đó là trong cấu hình nhà máy và thùng chứa.

[TestClass] 
public class UnitTest1 
{ 
    private IUnityContainer _container; 

    [TestInitialize] 
    public void InitializeTest() 
    { 
     _container = new UnityContainer(); 
     var configurer = new ContainerConfiguration(); 
     configurer.Configure(_container); 
    } 

    [TestCleanup] 
    public void CleanupTest() 
    { 
     _container.Dispose(); 
    } 

    [TestMethod] 
    public void ThingINeedFactory_CreatesExpectedType() 
    { 
     var factory = _container.Resolve<IThingINeedFactory>(); 
     var needsThing = new NeedsThing(factory); 
     var output = needsThing.PerformSomeFunction(ThingTypes.B); 
     Assert.AreEqual(output, typeof(ThingB).Name); 
    } 

    [TestMethod] 
    public void ThingINeedFactory_CreatesDefaultyTpe() 
    { 
     var factory = _container.Resolve<IThingINeedFactory>(); 
     var needsThing = new NeedsThing(factory); 
     var output = needsThing.PerformSomeFunction(ThingTypes.D); 
     Assert.AreEqual(output, typeof(ThingC).Name); 
    } 
} 

Cùng một nhà máy này có thể được thực hiện bằng Windsor và nhà máy trong ví dụ Windsor có thể được thực hiện trong Unity.

Các vấn đề liên quan