Cách tốt nhất để xây dựng một nhà máy bằng NInject là gì?


27

Tôi khá thoải mái với việc tiêm phụ thuộc bằng NInject trong MVC3. Khi làm việc trong một ứng dụng MVC3, tôi đã phát triển một Nhà máy tạo bộ điều khiển tùy chỉnh bằng NInject, do đó, bất kỳ bộ điều khiển nào được tạo sẽ có các phụ thuộc được đưa vào trong nó thông qua Nhà máy điều khiển này.

Bây giờ tôi đang bắt đầu phát triển một ứng dụng windows, tôi muốn sử dụng Application Dependency Injection. tức là Mọi đối tượng phải được tạo thông qua NInject, để dễ dàng Kiểm tra đơn vị. Vui lòng hướng dẫn tôi để đảm bảo rằng mọi đối tượng được tạo phải chỉ là Nhà máy NInject.

Ví dụ: nếu trên bất kỳ cửa sổ nào trong Button_Clicksự kiện tôi viết:

TestClass testClass = new TestClass()

TestClasscó bất kỳ sự phụ thuộc vào, nói, ITestsau đó nó phải được giải quyết tự động. Tôi biết tôi có thể sử dụng:

Ikernel kernel = new StandardKenel()
//AddBinding()
TestClass testClass = kenel.get<TestClass>();

Nhưng tôi thấy thật tẻ nhạt khi làm điều này mỗi khi tôi muốn tạo một đối tượng. Nó cũng buộc các nhà phát triển tạo ra đối tượng theo một cách cụ thể. Nó có thể được làm tốt hơn?

Tôi có thể có một kho lưu trữ trung tâm để tạo đối tượng và sau đó mọi tạo đối tượng sẽ tự động sử dụng kho lưu trữ đó không?


1
Xin chào Pravin Patil: câu hỏi tuyệt vời. Tôi đã thực hiện một thay đổi nhỏ cho tiêu đề của bạn để làm cho nó rõ ràng hơn về những gì bạn đang hỏi; cảm thấy tự do để sửa đổi nếu tôi bỏ lỡ đánh dấu.

@MarkTrapp: Cảm ơn vì tiêu đề phù hợp. Tôi đã bỏ lỡ khẩu hiệu đó ...
Pravin Patil

Như một lưu ý phụ, dự án được đánh vần là "Ninject", không phải "NInject". Mặc dù có thể đó là En-Tiêm, nhưng ngày nay họ chơi theo chủ đề nin-ja khá nhiều. :) ninject.org
Cornelius

Câu trả lời:


12

Đối với các ứng dụng khách, tốt nhất là điều chỉnh một mẫu như MVP (hoặc MVVM) và sử dụng liên kết dữ liệu từ biểu mẫu đến ViewModel hoặc Presenter bên dưới.

Đối với ViewModels, bạn có thể tiêm các phụ thuộc cần thiết bằng cách sử dụng Trình xây dựng tiêu chuẩn.

Trong Thành phần gốc của ứng dụng, bạn có thể kết nối toàn bộ biểu đồ đối tượng cho ứng dụng của mình. Bạn không cần sử dụng DI Container (như Ninject) cho việc này, nhưng bạn có thể.


7

Các ứng dụng Windows Forms thường có một điểm nhập giống như thế này:

    // Program.cs
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }

Nếu bạn thực hiện điểm này trong mã gốc thành phần của mình , bạn có thể giảm đáng kể số lượng địa điểm mà bạn có mã gọi rõ ràng Ninject như thể đó là Trình định vị dịch vụ.

    // Program.cs
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        var kernel = InitializeNinjectKernel();
        Application.Run(kernel.Get<MainForm>());
    }

Từ thời điểm này, bạn tiêm tất cả các phụ thuộc của bạn thông qua tiêm constructor.

public MainForm(TestClass testClass) {
    _testClass = testClass;
}

Nếu "sự phụ thuộc" của bạn là thứ bạn cần để có thể sản xuất nhiều lần, thì thứ bạn thực sự cần là một nhà máy:

public MainForm(IFactory<TestClass> testClassFactory) {
    _testClassFactory = testClassFactory;
}

...
var testClass = _testClassFactory.Get();

Bạn có thể triển khai giao diện IFactory theo cách này để tránh phải tạo ra rất nhiều triển khai một lần:

public class InjectionFactory<T> : IFactory<T>, IObjectFactory<T>, IDependencyInjector<T>
{
    private readonly IKernel _kernel;
    private readonly IParameter[] _contextParameters;

    public InjectionFactory(IContext injectionContext)
    {
        _contextParameters = injectionContext.Parameters
            .Where(p => p.ShouldInherit).ToArray();
        _kernel = injectionContext.Kernel;
    }

    public T Get()
    {
        try
        {
            return _kernel.Get<T>(_contextParameters.ToArray());
        }
        catch (Exception e)
        {
            throw new Exception(
                string.Format("An error occurred while attempting to instantiate an object of type <{0}>",
                typeof(T)));
        }
    }

...
Bind(typeof (IFactory<>)).To(typeof (InjectionFactory<>));
Bind(typeof (IContext)).ToMethod(c => c.Request.ParentContext);

Xin vui lòng, bạn có thực hiện đầy đủ cho nhà máy này?
Tebo

@ColourBlend: Không, nhưng nếu bạn thoát khỏi các giao diện khác mà tôi đã InjectionFactory<T>triển khai, thì mã được cung cấp sẽ hoạt động tốt. Có điều gì đặc biệt mà bạn đang gặp vấn đề không?
StriplingWar Warrior

Tôi đã thực hiện nó, tôi chỉ muốn biết liệu có những thứ thú vị khác trong lớp không.
Tebo

@Tebo: Tôi vừa mới triển khai một vài giao diện liên quan đến DI khác, giống như một nhà máy mà bạn có thể chuyển qua Type, nhưng sẽ đảm bảo rằng các đối tượng mà nó ngậm nước để Typethực hiện hoặc mở rộng một loại chung chung nhất định. Không có gì quá đặc biệt.
StriplingWar Warrior

4

Tôi luôn viết một trình bao bọc Bộ điều hợp cho bất kỳ Container IoC nào, trông giống như sau:

public static class Ioc
{
    public static IIocContainer Container { get; set; }
}

public interface IIocContainer 
{
    object Get(Type type);
    T Get<T>();
    T Get<T>(string name, string value);
    void Inject(object item);
    T TryGet<T>();
}

Đối với Ninject, cụ thể, lớp Adaptor cụ thể trông như thế này:

public class NinjectIocContainer : IIocContainer
{
    public readonly IKernel Kernel;
    public NinjectIocContainer(params INinjectModule[] modules) 
    {
        Kernel = new StandardKernel(modules);
        new AutoWirePropertyHeuristic(Kernel);
    }

    private NinjectIocContainer()
    {
        Kernel = new StandardKernel();
        Kernel.Load(AppDomain.CurrentDomain.GetAssemblies());

        new AutoWirePropertyHeuristic(Kernel);
    }

    public object Get(Type type)
    {
        try
        {
            return Kernel.Get(type);
        }
        catch (ActivationException exception)
        {
            throw new TypeNotResolvedException(exception);
        }              
    }

    public T TryGet<T>()
    {
        return Kernel.TryGet<T>();
    }

    public T Get<T>()
    {
        try
        {
            return Kernel.Get<T>();
        }
        catch (ActivationException exception)
        {
            throw new TypeNotResolvedException(exception);
        }           
    }

    public T Get<T>(string name, string value)
    {
        var result = Kernel.TryGet<T>(metadata => metadata.Has(name) &&
                     (string.Equals(metadata.Get<string>(name), value,
                                    StringComparison.InvariantCultureIgnoreCase)));

        if (Equals(result, default(T))) throw new TypeNotResolvedException(null);
            return result;
    }

    public void Inject(object item)
    {
        Kernel.Inject(item);
    }
}

Lý do chính để thực hiện điều này là để trừu tượng hóa khung IoC, vì vậy tôi có thể thay thế nó bất cứ lúc nào - với điều kiện là sự khác biệt giữa các khung nói chung là về cấu hình thay vì sử dụng.

Nhưng, như một phần thưởng, mọi thứ cũng trở nên dễ dàng hơn rất nhiều khi sử dụng khung IoC bên trong các khung công tác khác vốn không hỗ trợ nó. Ví dụ, đối với WinForms, có hai bước:

Trong phương thức chính của bạn, chỉ cần khởi tạo một container trước khi làm bất cứ điều gì khác.

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        try
        {
            Ioc.Container = new NinjectIocContainer( /* include modules here */ );
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MyStartupForm());
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }
}

Và sau đó có một Biểu mẫu cơ sở, từ đó các hình thức khác xuất phát, gọi chính là Tiêm.

public IocForm : Form
{
    public IocForm() : base()
    {
        Ioc.Container.Inject(this);
    }
}

Điều này cho biết heuristic tự động nối dây để cố gắng đệ quy tất cả các thuộc tính ở dạng phù hợp với các quy tắc được thiết lập trong các mô-đun của bạn.


Giải pháp rất hay ..... Tôi sẽ thử.
Pravin Patil

10
Đó là một Trình định vị dịch vụ, đó là một ý tưởng tồi tệ ngoạn mục: blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPotype.aspx
Mark Seemann

2
@MarkSeemann: Công cụ định vị dịch vụ là một ý tưởng tồi, nếu bạn truy cập nó từ mọi nơi, thay vì để nó nối các đối tượng cấp cao nhất của bạn càng xa càng tốt. Đọc bình luận riêng của Mark, cách trang này một chút: "Trong những trường hợp như vậy, bạn thực sự không có cách nào khác ngoài việc di chuyển Root Root vào từng đối tượng (ví dụ Trang) và để DI Container của bạn nối kết các phụ thuộc của bạn từ đó. Điều này có thể giống như Mẫu chống định vị dịch vụ, nhưng không phải vì bạn vẫn giữ mức sử dụng container ở mức tối thiểu. " (Chỉnh sửa: Đợi đã, bạn là Mark! Vậy sự khác biệt là gì?)
pdr

1
Sự khác biệt là bạn vẫn có thể bảo vệ phần còn lại của cơ sở mã của mình khỏi Trình soạn thảo, thay vì cung cấp Trình định vị dịch vụ Singleton cho bất kỳ lớp nào.
Mark Seemann

2
@pdr: Theo kinh nghiệm của tôi, nếu bạn đang cố gắng đưa dịch vụ vào những thứ như các lớp thuộc tính, thì bạn không tách biệt mối quan tâm đúng cách. Có những trường hợp khung mà bạn đang sử dụng khiến cho thực tế không thể sử dụng phép tiêm phụ thuộc phù hợp và đôi khi chúng tôi buộc phải sử dụng trình định vị dịch vụ, nhưng tôi chắc chắn sẽ cố gắng thực hiện DI càng xa càng tốt trước khi quay lại điều này mẫu.
StriplingWar Warrior

1

Việc sử dụng tốt Dependency Injection thường dựa vào việc tách mã tạo ra các đối tượng và logic nghiệp vụ thực tế. Nói cách khác, tôi sẽ không muốn nhóm của mình thường xuyên sử dụng newvà tạo một thể hiện của một lớp theo cách đó. Một khi nó được thực hiện, không có cách nào để dễ dàng trao đổi loại đã tạo cho loại khác, vì bạn đã chỉ định loại cụ thể.

Vì vậy, có hai cách để khắc phục điều này:

  1. Tiêm các trường hợp mà một lớp sẽ cần. Trong ví dụ của bạn, hãy tiêm a TestClassvào Mẫu Windows của bạn để nó đã có một thể hiện khi nó cần. Khi Ninject khởi tạo biểu mẫu của bạn, nó sẽ tự động tạo ra sự phụ thuộc.
  2. Trong trường hợp bạn thực sự không muốn tạo một cá thể cho đến khi bạn cần nó, bạn có thể đưa một nhà máy vào logic kinh doanh. Ví dụ: bạn có thể đưa một biểu mẫu IKernelvào Windows Form của mình và sau đó sử dụng biểu mẫu đó để khởi tạo TestClass. Tùy thuộc vào phong cách của bạn, có nhiều cách khác để thực hiện điều này (tiêm một lớp nhà máy, đại biểu nhà máy, v.v.).

Việc này giúp dễ dàng trao đổi cả loại TestClass cụ thể, cũng như sửa đổi cấu trúc thực tế của lớp thử nghiệm mà không thực sự sửa đổi mã sử dụng lớp thử nghiệm.


1

Tôi đã không sử dụng Ninject, nhưng cách tạo công cụ tiêu chuẩn khi bạn đang sử dụng IoC là thực hiện thông qua Func<T>đó Tlà loại đối tượng bạn muốn tạo. Vì vậy, nếu đối tượng T1cần tạo các đối tượng kiểu T2thì hàm tạo của T1phải có tham số kiểu Func<T1>sau đó được lưu trữ dưới dạng trường / thuộc tính của T2. Bây giờ khi bạn muốn tạo các đối tượng kiểu T2trong T1bạn gọi Func.

Điều này hoàn toàn tách bạn ra khỏi khuôn khổ IoC của bạn và là cách đúng để viết mã theo tư duy IoC.

Nhược điểm của việc này là nó có thể gây khó chịu khi bạn cần nối dây thủ công Funchoặc ví dụ khi người tạo của bạn yêu cầu một số tham số, vì vậy IoC không thể tự động cung Funccấp cho bạn.

http://code.google.com.vn/p/autofac/wiki/RelationshipTypes

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.