2008-12-11 41 views
14

Tôi đang nhìn vào tiêm depency, tôi có thể thấy những lợi ích nhưng tôi gặp vấn đề với cú pháp nó tạo ra. Tôi có ví dụ nàylựa chọn thay thế tiêm phụ thuộc

public class BusinessProducts 
{ 
    IDataContext _dx; 

    BusinessProducts(IDataContext dx) 
    { 
     _dx = dx; 
    } 

    public List<Product> GetProducts() 
    { 
    return dx.GetProducts(); 
    } 
} 

Vấn đề là tôi không muốn viết

BusinessProducts bp = new BusinessProducts(dataContextImplementation); 

tôi sẽ tiếp tục viết

BusinessProducts bp = new BusinessProducts(); 

vì tôi cảm nhận được sự thay thế đầu tiên chỉ cảm thấy unatural . Tôi không muốn biết những gì BusinessProduct "phụ thuộc" trên để có được các sản phẩm, tôi cũng cảm thấy nó làm cho mã của tôi không thể đọc được. Có bất kỳ lựa chọn thay thế nào cho phương pháp này vì tôi muốn giữ nguyên cú pháp ban đầu để tạo đối tượng nhưng tôi vẫn muốn giả mạo các phụ thuộc khi kiểm thử đơn vị hay khuôn khổ tiêm phụ thuộc này có thể làm cho tôi không?

tôi mã hóa trong C# nhưng lựa chọn thay thế từ các ngôn ngữ khác được hoan nghênh

+3

Vâng, nếu bạn đang sử dụng từ khóa "mới" trực tiếp thay vì yêu cầu đối tượng bạn không sử dụng tiêm phụ thuộc. –

+4

@Pop, bạn đang nghĩ đến một khung DI, chứ không phải là DI. Trong DI mẫu, việc tiêm một thể hiện của lớp mà bạn phụ thuộc vào trong một hàm tạo là hoàn toàn hợp lý. Một cách khác là làm điều đó thông qua các thuộc tính. – tvanfosson

+1

@Pop Catalin - Đây là ví dụ về DI. Ví dụ của đối tượng được tiêm là "IDataContext" không phải là "BusinessProducts". – Owen

Trả lời

9

Tôi sử dụng nhà máy cho ngữ cảnh của mình và tiêm nó, cung cấp mặc định phù hợp nếu nhà máy được cung cấp không có giá trị. Tôi làm điều này vì hai lý do. Đầu tiên, tôi sử dụng bối cảnh dữ liệu như một đơn vị của đối tượng phạm vi công việc, vì vậy tôi cần để có thể tạo ra chúng khi cần thiết, không giữ cho chúng xung quanh. Thứ hai, tôi chủ yếu sử dụng DI để tăng khả năng thử nghiệm, với việc tách riêng chỉ là sự cân nhắc thứ cấp.

Vì vậy, lớp sản phẩm doanh nghiệp của tôi sẽ như thế nào:

public class BusinessProducts 
{ 
    private IDataContextFactory DataContextFactory { get; set; } // my interface 

    public BusinessProducts() : this(null) {} 

    public BusinessProducts(IDataContextFactory factory) 
    { 
      this.DataContext = factory ?? new BusinessProductsDataContextFactory(); 
    } 

    public void DoSomething() 
    { 
      using (DataContext dc = this.DataContextFactory().CreateDataContext()) 
      { 
      ... 
      } 
    } 

Một thay thế cho điều này là làm cho tài sản nhà máy công khai settable và tiêm một nhà máy thay thế bằng cách thiết lập tài sản. Dù bằng cách nào nếu bạn muốn giữ hàm tạo null, bạn sẽ cần phải cung cấp mặc định.

3

Có hai trường hợp riêng biệt ở đây:

Trong mã sản xuất, bạn sẽ không bao giờ ghi

new BusinessProducts(dataContextImplementation) 

vì dependency injection thông thường sẽ được tạo ra hệ thống phân cấp đối tượng đầy đủ cho bạn. Đây là bản chất "lan truyền" của các mô hình tiêm phụ thuộc, chúng có xu hướng kiểm soát hoàn toàn việc tạo ra dịch vụ của bạn.

Trong mã thử nghiệm đơn vị, bạn thường sẽ tự tạo mã này, nhưng thường thì bạn sẽ cung cấp đối tượng giả hoặc thực thi sơ khai dataContextImplementation. Vì vậy, thông thường bạn sẽ được tiêm một đối tượng mà không có một số lượng lớn các phụ thuộc tiếp theo.

+1

@ krosenvold - Tôi nghĩ bạn đang nghĩ về các khuôn khổ DI, chứ không phải là DI. Các mô hình chỉ nói rằng bạn làm chính xác đó - tiêm phụ thuộc thông qua constructor hoặc setters tài sản. Bạn có thể sử dụng DI mẫu mà không cần sử dụng khung DI. – tvanfosson

6

Bạn có thể tạo một nhà máy. DI container là tốt nhất cho wirings xảy ra tại thời gian thiết lập - không phải lúc chạy (Vì đây có vẻ là một trường hợp). Các nhà máy có thể được thực hiện theo nhiều cách khác nhau, tùy thuộc vào mức độ cần thiết của nó và bạn cần sử dụng bao nhiêu địa điểm.

1

Nói chung, khung chính sẽ có logic để xây dựng toàn bộ cây đối tượng. Ví dụ, thay vì

new SomeObjectO(diContext) 

bạn sẽ gọi khuôn khổ như thế này:

DIFramework.GetNew<SomeObjectO>(); 

hoặc

DIFramework.Get<SomeObject>(); 

Một khuôn khổ thú vị để xem xét nếu bạn muốn tìm hiểu về DI và quá trình này là các dự án của Unity và Object Builder của Microsoft.

+0

thx, đây là câu trả lời rõ ràng – terjetyl

+1

Tại sao hầu hết mọi người trả lời là khó hiểu DI mô hình với khung DI? Các khung công tác DI sử dụng mẫu DI, nhưng bạn không phải sử dụng một khung công tác DI chỉ để triển khai thực hiện mẫu DI. – tvanfosson

+0

Tôi đồng ý với bạn tvanfosson. Câu trả lời này chỉ giúp tôi hiểu những gì một khuôn khổ DI làm cho bạn – terjetyl

6

Tôi thường sẽ có một hàm tạo rỗng, sử dụng một cá thể rắn (hoặc một cá thể được tạo bởi IoC), một với một DI. tức là

public class BusinessProducts 
{ 
    IDataContext _dx; 

    BusinessProducts() 
    { 
     _dx = new SolidDataContext(); 
    } 

    BusinessProducts(IDataContext dx) 
    { 
     _dx = dx; 
    } 
} 

Bằng cách này bạn có thể sử dụng DI để ghi đè trường hợp mặc định trong thử nghiệm kiểm tra đơn vị.

+0

Tôi đã sử dụng kỹ thuật này nhiều lần để tiêm trong các triển khai khác nhau cho mục đích thử nghiệm đơn vị. Nó rất hữu ích nếu bạn không có một thùng chứa DI. – RichardOD

1

Nếu bạn thực sự không thích tiêm trường hợp này trong hàm tạo, bạn có thể thử sử dụng CommonServiceLocator với khung tiêm .NET depedency tương thích yêu thích của bạn. Điều này sẽ cho phép bạn viết mã như thế này:

public class BusinessProducts 
{ 
    IDataContext _dx; 

    BusinessProducts() 
    { 
     _dx = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IDataContext>(); 
    } 

    public List<Product> GetProducts() 
    { 
    return dx.GetProducts(); 
    } 
} 

Tuy nhiên, xin hãy cẩn thận rằng đây không phải là điều mà hầu hết mọi người sẽ mong đợi khi họ biết rằng bạn sử dụng một khuôn khổ dependency injection. Tôi nghĩ rằng nó phổ biến hơn nhiều để sử dụng một khuôn khổ tiêm phụ thuộc và cho phép nó tạo ra tất cả các đối tượng cho bạn.

BusinessProducts bp = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<BusinessProducts>(); 

Nếu bạn muốn tránh đường dẫn khuôn tiêm phụ thuộc, sử dụng nhà máy có lẽ là cách tốt nhất để đi.

1

Có một kỹ thuật gọi là DI nghèo của con người trông như thế này

public class BusinessProducts 
{ 
    IDataContext _dx; 

    BusinessProducts() : this(new DataContext()) {} 

    BusinessProducts(IDataContext dx) 
    { 
     _dx = dx; 
    } 

    public List<Product> GetProducts() 
    { 
    return dx.GetProducts(); 
    } 
} 

Đây không phải là lý tưởng vì nó quan hệ bạn thực hiện nhưng một bước đệm tốt của nó đối với đang tách riêng. điều này tương tự như @tvanfosson nhưng đơn giản hơn rất nhiều.

Tôi thứ hai đề xuất cho Windsor

1

Mã của tôi sẽ tham chiếu Microsoft Unity nhưng tôi chắc chắn mã này khá phù hợp với tất cả các khung DI. Nếu bạn đang sử dụng DI một cách chính xác bạn không bao giờ cần phải gọi BusinessObject mới (new dataContext) hiệp hội DI sẽ xử lý tất cả cho bạn.

Ví dụ của tôi sẽ lâu hơn một chút vì tôi sẽ dán một số mã tôi sử dụng để chạy trang web Trình xem mô hình Xem đầy đủ DI được tải bởi Unity. (Nếu bạn muốn nguồn đầy đủ kiểm tra blog của tôi và tải về từ máy chủ Assembla SVN của tôi)

tải container (có thể trong mã như tôi thích hoặc sử dụng cấu hình)

protected void Application_Start(object sender, EventArgs e) 
{ 
    Application.GetContainer() 
     // presenters/controllers are per request     
     .RegisterType<IEmployeeController, EmployeeController>(new ContextLifetimeManager<IEmployeeController>()) 

     //Data Providers are Per session     
     .RegisterType<IEmployeeDataProvider, EmployeeDataProvider>(new SessionLifetimeManager<IEmployeeDataProvider>()) 

     //Session Factory is life time 
     .RegisterType<INHibernateSessionManager, NHibernateSessionManager>(new ContainerControlledLifetimeManager()); 
} 

Tuỳ chỉnh HTTP cuộc gọi mô-đun Phương thức xây dựng Unity cho mỗi trang trong lời gọi OnPreRequest.

private static void OnPreRequestHandlerExecute(object sender, EventArgs e) 
{ 
    var handler = HttpContext.Current.Handler; 
    HttpContext.Current.Application.GetContainer().BuildUp(handler.GetType(), handler); 

    // User Controls are ready to be built up after the page initialization is complete 
    var page = HttpContext.Current.Handler as Page; 
    if (page != null) 
    { 
     page.InitComplete += OnPageInitComplete; 
    } 
} 

người dẫn chương trình trang chứa trang trí với [Thuộc] thuộc tính

public partial class Employees : Page, IEmployeeView 
{ 
    private EmployeePresenter _presenter; 

    [Dependency] 
    public EmployeePresenter Presenter 
    { 
     set 
     { 
      _presenter = value; 
      _presenter.View = this; 
     } 
    } 
} 

Presenter với phương pháp InjectionConstructor

public class EmployeePresenter : Presenter<IEmployeeView> 
{ 
    private readonly IEmployeeController _controller; 

    [InjectionConstructor] 
    } 
    public EmployeePresenter(IEmployeeController controller) 
    { 
     _controller = controller; 
} 

điều khiển sau phù hợp với

public class EmployeeController : IEmployeeController 
{ 
    private readonly IEmployeeDataProvider _provider; 

    [InjectionConstructor] 
    public EmployeeController(IEmployeeDataProvider DataProvider) 
    { 
     _provider = DataProvider; 
    } 
} 

Cùng với pro vider

public class EmployeeController : IEmployeeController 
{ 
    private readonly IEmployeeDataProvider _provider; 

    [InjectionConstructor] 
    public EmployeeController(IEmployeeDataProvider DataProvider) 
    { 
     _provider = DataProvider; 
    } 
} 

Cuối cùng, trình quản lý phiên, chỉ chứa một hàm tạo thông thường.

public class NHibernateSessionManager : INHibernateSessionManager 
{ 
    private readonly ISessionFactory _sessionFactory; 

    public NHibernateSessionManager() 
    {    
     _sessionFactory = GetSessionFactory(); 
    } 
} 

Vì vậy, điều xảy ra khi một yêu cầu trang được khởi động phương thức BuildUp() được gọi trên trang HttpModule. Unity sau đó nhìn thấy thuộc tính được đánh dấu bằng thuộc tính Dependency và sẽ kiểm tra nó là container để xem bên trong nó có tồn tại một đối tượng EmployeePresenter hay không.

Vì không có đối tượng như vậy trong vùng chứa, khi đó, nó sẽ cố gắng tạo một EmployeePresenter. Sau khi kiểm tra để tạo ra lớp mà nó nhìn thấy bên trong Trình bày nó yêu cầu một hàm tạo cần một IEmployeeController được tiêm vào nó. Vì thùng chứa thực sự có một trình quản lý cho bộ điều khiển nên nó sẽ thấy nếu một thể hiện của nó tồn tại trong vùng chứa mà trên đầu của yêu cầu trang không tồn tại, vì vậy nó sẽ đi để khởi tạo bộ điều khiển.

Unity sau đó sẽ thấy bộ điều khiển yêu cầu một IEmployeeDataProvider tiêm vào nó, và nó sẽ tiếp tục quá trình này cho đến khi nó cuối cùng đến điểm mà Nhà cung cấp cần trình quản lý phiên được tiêm. Vì trình quản lý phiên không còn cần tiêm Unity thì sẽ tạo một thể hiện của trình quản lý phiên lưu trữ nó trong thùng chứa cho nó được đưa vào ContainerLifeTimeManager, đưa nó vào trong Nhà cung cấp và lưu trữ cá thể đó, và cứ thế xuống đến nơi nó đã tạo xong Phụ thuộc EmployeePresenter cho trang.

4

Cảm xúc của bạn, trong khi hợp lệ, bị đặt không đúng chỗ.

Mẫu Dependency Injection là một ứng dụng trực tiếp của nguyên tắc Inversion of Control.

Điều này có nghĩa là, thay vì lớp học của bạn kiểm soát các phiên bản của các lớp khác mà nó tiêu thụ, mối quan hệ đó là ngược và phụ thuộc được cung cấp cho nó.

Như vậy, các lớp của bạn tự nhiên phơi bày các phụ thuộc của chúng thông qua các đối số hoặc thuộc tính của hàm tạo. Hiển thị thái độ khinh thị đối với cấu trúc này cho biết bạn chưa thực sự tạo ra hình mẫu.

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