2012-04-23 28 views
21

Nếu tôi có đoạn mã sau:IoC (Ninject) và Nhà máy

public class RobotNavigationService : IRobotNavigationService { 
    public RobotNavigationService(IRobotFactory robotFactory) { 
    //... 
    } 
} 
public class RobotFactory : IRobotFactory { 
    public IRobot Create(string nameOfRobot) { 
    if (name == "Maximilian") { 
     return new KillerRobot(); 
    } else { 
     return new StandardRobot(); 
    } 
    } 
} 

Câu hỏi của tôi là cách thích hợp để làm Inversion of Control đây là những gì? Tôi không muốn thêm bê tông KillerRobot và StandardRobot vào lớp Nhà máy phải không? Và tôi không muốn mang chúng qua một IoC.Get <> phải không? bc đó sẽ là vị trí dịch vụ không đúng sự thật của IoC? Có cách nào tốt hơn để tiếp cận vấn đề của chuyển bê tông tại thời gian chạy?

+2

Có thể muốn kiểm tra mã của bạn - dòng đầu tiên không phải là pháp C#. –

+0

Xin lỗi. Cảm ơn lời nhắc. Nghĩ rằng tôi đã sửa nó trước khi đăng. Sửa ngay bây giờ. – BuddyJoe

Trả lời

29

Đối với mẫu của bạn, bạn có triển khai nhà máy hoàn hảo và tôi sẽ không thay đổi bất cứ điều gì.

Tuy nhiên, tôi nghi ngờ rằng các lớp KillerRobot và StandardRobot của bạn thực sự có các phụ thuộc của riêng chúng. Tôi đồng ý rằng bạn không muốn để lộ container IoC của bạn để RobotFactory.

Một lựa chọn là sử dụng phần mở rộng nhà máy Ninject:

https://github.com/ninject/ninject.extensions.factory/wiki

Nó cung cấp cho bạn hai cách để bơm nhà máy - bởi giao diện, và bằng cách tiêm một Func mà trả về một iRobot (hoặc bất kỳ).

mẫu cho giao diện dựa trên sự sáng tạo nhà máy: https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface

mẫu cho Func dựa trên: https://github.com/ninject/ninject.extensions.factory/wiki/Func

Nếu bạn muốn, bạn cũng có thể làm điều đó bằng cách gắn một func trong mã IoC Khởi tạo của bạn. Một cái gì đó như:

var factoryMethod = new Func<string, IRobot>(nameOfRobot => 
         { 
          if (nameOfRobot == "Maximilian") 
          { 
           return _ninjectKernel.Get<KillerRobot>(); 
          } 
          else 
          { 
           return _ninjectKernel.Get<StandardRobot>(); 
          } 

         }); 
_ninjectKernel.Bind<Func<string, IRobot>>().ToConstant(factoryMethod); 

dịch vụ điều hướng của bạn sau đó có thể trông giống như:

public class RobotNavigationService 
    { 
     public RobotNavigationService(Func<string, IRobot> robotFactory) 
     { 
      var killer = robotFactory("Maximilian"); 
      var standard = robotFactory(""); 
     } 
    } 

Tất nhiên, vấn đề với phương pháp này là bạn đang viết các phương pháp nhà máy ngay trong IoC khởi của bạn - có lẽ không phải là tốt nhất tradeoff ...

Tiện ích mở rộng của nhà máy cố gắng giải quyết vấn đề này bằng cách cung cấp cho bạn một số phương pháp dựa trên quy ước - do đó cho phép bạn giữ lại chuỗi DI bình thường với việc bổ sung các phụ thuộc theo ngữ cảnh.

+0

Hãy thử tiện ích mở rộng - bạn đã bán cho tôi! +1 & answer – BuddyJoe

+0

@BuddyJoe có bao giờ nhận được mã đang chạy bằng phương thức Factory ở trên không? – ct5845

+0

Tôi đã làm. Và nếu tôi nhớ mã cuối cùng rất gần với điều này. – BuddyJoe

3

Tôi không muốn thêm bê tông KillerRobot và StandardRobot vào lớp Nhà máy?

Tôi khuyên bạn nên làm. Mục đích của một nhà máy là gì nếu không thể khởi tạo các vật thể cụ thể? Tôi nghĩ rằng tôi có thể thấy bạn đến từ đâu - nếu IRobot mô tả một hợp đồng, không nên thùng chứa chịu trách nhiệm tạo ra nó? Đó không phải là những gì container cho?

Có thể. Tuy nhiên, việc trả lại các nhà máy bê tông chịu trách nhiệm cho các đối tượng ing new có vẻ là một mô hình khá chuẩn trong thế giới IoC. Tôi không nghĩ rằng nó là nguyên tắc để có một nhà máy cụ thể làm một số công việc thực tế.

+3

Mặc dù không được minh họa bởi mã mẫu của mình, tôi nghĩ rằng vấn đề ông có là KillerRobot và StandardRobot thực sự có thêm phụ thuộc cần phải được giải quyết bởi ninject là tốt. –

+0

Và sau đó làm thế nào bạn sẽ xử lý cho phép nói nếu KillerRobot và Robot tiêu chuẩn có phụ thuộc? – BuddyJoe

+1

Nếu bạn sử dụng giao diện dựa trên giao diện hoặc func trong phần mở rộng của nhà máy, độ phân giải phụ thuộc đệ quy sẽ diễn ra. –

2

Cách bạn nên làm:

kernel.Bind<IRobot>().To<KillingRobot>("maximilliam"); 
kernel.Bind<IRobot>().To<StandardRobot>("standard"); 
kernel.Bind<IRobotFactory>().ToFactory(); 

public interface IRobotFactory 
{ 
    IRobot Create(string name); 
} 

Nhưng cách này tôi nghĩ bạn bị mất tên null, nên khi gọi IRobotFactory.Create bạn phải đảm bảo đúng tên sẽ được gửi qua tham số.

Khi sử dụng ToFactory() trong giao diện ràng buộc, tất cả những gì bạn làm là tạo proxy bằng cách sử dụng Castle (hoặc proxy động) nhận IResolutionRoot và gọi hàm Get().

+0

Yea Tôi thích làm theo cách này +1 – ppumkin

0

Tôi đang tìm cách dọn dẹp một câu lệnh chuyển đổi lớn trả về một lớp C# để thực hiện một số công việc (mã nguồn ở đây).

Tôi không muốn ánh xạ rõ ràng từng giao diện để triển khai thực hiện cụ thể trong mô-đun ninject (về cơ bản là mô phỏng trường hợp chuyển đổi dài, nhưng trong tệp khác) nên tôi thiết lập mô-đun để tự động ràng buộc tất cả các giao diện:

public class FactoryModule: NinjectModule 
{ 
    public override void Load() 
    { 
     Kernel.Bind(x => x.FromThisAssembly() 
          .IncludingNonPublicTypes() 
          .SelectAllClasses() 
          .InNamespaceOf<FactoryModule>() 
          .BindAllInterfaces() 
          .Configure(b => b.InSingletonScope())); 
    } 
} 

Sau đó tạo lớp nhà máy, thực hiện StandardKernal mà sẽ nhận các giao diện cụ thể và triển khai của họ thông qua một ví dụ singleton sử dụng một Ikernal:

public class CarFactoryKernel : StandardKernel, ICarFactoryKernel{ 
    public static readonly ICarFactoryKernel _instance = new CarFactoryKernel(); 

    public static ICarFactoryKernel Instance { get => _instance; } 

    private CarFactoryKernel() 
    { 
     var carFactoryModeule = new List<INinjectModule> { new FactoryModule() }; 

     Load(carFactoryModeule); 
    } 

    public ICar GetCarFromFactory(string name) 
    { 
     var cars = this.GetAll<ICar>(); 
     foreach (var car in cars) 
     { 
      if (car.CarModel == name) 
      { 
       return car; 
      } 
     } 

     return null; 
    } 
} 

public interface ICarFactoryKernel : IKernel 
{ 
    ICar GetCarFromFactory(string name); 
} 

Sau đó thực hiện StandardKernel bạn có thể nhận được ở một ny giao diện bởi các định danh của bạn lựa chọn trên giao diện trang trí lớp học của bạn.

ví dụ .:

public interface ICar 
{ 
    string CarModel { get; } 
    string Drive { get; } 
    string Reverse { get; } 
} 

public class Lamborghini : ICar 
{ 
    private string _carmodel; 
    public string CarModel { get => _carmodel; } 
    public string Drive => "Drive the Lamborghini forward!"; 
    public string Reverse => "Drive the Lamborghini backward!"; 

    public Lamborghini() 
    { 
     _carmodel = "Lamborghini"; 
    } 
} 

Cách sử dụng:

 [Test] 
    public void TestDependencyInjection() 
    { 
     var ferrari = CarFactoryKernel.Instance.GetCarFromFactory("Ferrari"); 
     Assert.That(ferrari, Is.Not.Null); 
     Assert.That(ferrari, Is.Not.Null.And.InstanceOf(typeof(Ferrari))); 

     Assert.AreEqual("Drive the Ferrari forward!", ferrari.Drive); 
     Assert.AreEqual("Drive the Ferrari backward!", ferrari.Reverse); 

     var lambo = CarFactoryKernel.Instance.GetCarFromFactory("Lamborghini"); 
     Assert.That(lambo, Is.Not.Null); 
     Assert.That(lambo, Is.Not.Null.And.InstanceOf(typeof(Lamborghini))); 

     Assert.AreEqual("Drive the Lamborghini forward!", lambo.Drive); 
     Assert.AreEqual("Drive the Lamborghini backward!", lambo.Reverse); 
    } 
Các vấn đề liên quan