วิธีที่ดีที่สุดในการสร้างโรงงานโดยใช้ NInject คืออะไร


27

ฉันค่อนข้างสบายใจกับการฉีดแบบพึ่งพาโดยใช้ NInject ใน MVC3 ในขณะที่ทำงานในแอปพลิเคชัน MVC3 ฉันได้พัฒนา Factory Creation Factory แบบกำหนดเองโดยใช้ NInject ดังนั้นคอนโทรลเลอร์ใด ๆ ที่สร้างขึ้นจะมีการขึ้นต่อกันของมันผ่านทาง Factory Controller นี้

ตอนนี้ฉันเริ่มพัฒนาแอพพลิเคชัน windows ฉันต้องการใช้ Application Wide Dependency Injection เช่นวัตถุทุกชิ้นจะต้องสร้างผ่าน NInject เพื่อให้ง่ายต่อการทดสอบหน่วย โปรดแนะนำฉันเพื่อให้แน่ใจว่าวัตถุทุกชิ้นที่สร้างขึ้นต้องเป็นโรงงาน NInject เท่านั้น

ตัวอย่างเช่นถ้าในหน้าต่างใด ๆ ในรูปแบบButton_Clickเหตุการณ์ฉันเขียน:

TestClass testClass = new TestClass()

และTestClassมีการพึ่งพาพูดพูดITestแล้วมันจะต้องได้รับการแก้ไขโดยอัตโนมัติ ฉันรู้ว่าฉันสามารถใช้:

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

แต่ฉันคิดว่ามันน่าเบื่อที่จะทำสิ่งนี้ทุกครั้งที่ฉันต้องการสร้างวัตถุ นอกจากนี้ยังเป็นนักพัฒนากองกำลังในการสร้างวัตถุในวิธีเฉพาะ มันจะดีขึ้นไหม

ฉันสามารถมีที่เก็บส่วนกลางสำหรับการสร้างวัตถุแล้วทุกการสร้างวัตถุจะใช้ที่เก็บนั้นโดยอัตโนมัติ


1
สวัสดี Pravin Patil: คำถามที่ดี ฉันได้ทำการเปลี่ยนแปลงเล็กน้อยในชื่อของคุณเพื่อให้ชัดเจนเกี่ยวกับสิ่งที่คุณถาม อย่าลังเลที่จะแก้ไขหากฉันพลาดเครื่องหมาย

@ MarkTrapp: ขอบคุณสำหรับชื่อที่เหมาะสม ฉันพลาดสโลแกนนั้น ...
Pravin Patil

ในฐานะที่เป็นหมายเหตุด้านเล็กน้อยโครงการนี้สะกดว่า "Ninject" ไม่ใช่ "NInject" แม้ว่ามันอาจจะเป็น En-Inject ก็ตาม แต่พวกเขาก็เล่นในธีม nin-ja ค่อนข้างมากในทุกวันนี้ :) เทียบ ninject.org
Cornelius

คำตอบ:


12

สำหรับแอปพลิเคชันไคลเอนต์มักจะดีที่สุดในการปรับรูปแบบเช่น MVP (หรือ MVVM) และใช้การเชื่อมโยงข้อมูลจากแบบฟอร์มไปยัง ViewModel หรือผู้นำเสนอพื้นฐาน

สำหรับ ViewModels คุณสามารถฉีดการพึ่งพาที่จำเป็นโดยใช้ Constructor Injection มาตรฐาน

ในComposition Rootของแอปพลิเคชันของคุณคุณสามารถโยงกราฟวัตถุทั้งหมดสำหรับแอปพลิเคชันของคุณ คุณไม่จำเป็นต้องใช้ DI Container (เช่น Ninject) สำหรับสิ่งนี้ แต่ทำได้


7

แอปพลิเคชัน Windows Forms มักมีจุดเข้าใช้ที่มีลักษณะดังนี้:

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

หากคุณทำให้จุดนี้เป็นรหัสรากองค์ประกอบของคุณคุณสามารถลดจำนวนสถานที่ที่คุณมีรหัสการเรียกใช้ Ninject อย่างชัดเจนราวกับว่ามันเป็นผู้ให้บริการ

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

จากจุดนี้คุณฉีดการพึ่งพาทั้งหมดของคุณผ่านการฉีดคอนสตรัค

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

หาก "การพึ่งพา" ของคุณเป็นสิ่งที่คุณต้องการเพื่อให้สามารถผลิตได้หลายครั้งสิ่งที่คุณต้องการคือโรงงาน:

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

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

คุณสามารถใช้อินเตอร์เฟส IFactory ด้วยวิธีนี้เพื่อหลีกเลี่ยงการสร้างการนำไปใช้งานครั้งเดียวมากมาย:

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);

กรุณาคุณมีการใช้งานอย่างสมบูรณ์สำหรับโรงงานนี้หรือไม่?
Tebo

@ColourBlend: ไม่ แต่ถ้าคุณกำจัดอินเทอร์เฟซอื่น ๆ ที่ฉันInjectionFactory<T>นำมาใช้แล้วรหัสที่ระบุควรใช้งานได้ดี มีบางอย่างที่คุณกำลังมีปัญหาอยู่หรือไม่?
StriplingWarrior

ฉันนำไปใช้แล้วฉันแค่อยากรู้ว่ามีสิ่งที่น่าสนใจอื่น ๆ ในชั้นเรียนหรือไม่
Tebo

@Tebo: ฉันเพิ่งจะใช้อินเทอร์เฟซที่เกี่ยวข้องกับ DI อื่น ๆ สองสามอย่างเช่นโรงงานที่คุณสามารถส่งผ่านTypeไปยังซึ่งจะรับประกันได้ว่าวัตถุที่มันชุ่มชื้นสำหรับTypeการใช้งานนั้นหรือขยายประเภททั่วไปที่กำหนด ไม่มีอะไรพิเศษเกินไป
StriplingWarrior

4

ฉันมักจะเขียนตัวแปลงอะแดปเตอร์สำหรับคอนเทนเนอร์ IoC ใด ๆ ซึ่งมีลักษณะเช่นนี้:

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>();
}

สำหรับ Ninject โดยเฉพาะคลาส Adaptor คอนกรีตจะมีลักษณะดังนี้

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);
    }
}

เหตุผลหลักในการทำเช่นนี้คือการทำให้กรอบ IoC เป็นนามธรรมออกไปดังนั้นฉันสามารถแทนที่มันได้ทุกเมื่อ - เนื่องจากความแตกต่างระหว่างเฟรมเวิร์กโดยทั่วไปคือการกำหนดค่าแทนที่จะใช้งาน

แต่เป็นโบนัสสิ่งต่าง ๆ ก็กลายเป็นเรื่องง่ายขึ้นสำหรับการใช้กรอบ IoC ในกรอบอื่น ๆ ที่ไม่สนับสนุนโดยธรรมชาติ สำหรับ WinForms ตัวอย่างเช่นมันเป็นสองขั้นตอน:

ในวิธีการหลักของคุณเพียงยกตัวอย่างภาชนะก่อนที่จะทำอะไรอย่างอื่น

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());
        }
    }
}

และจากนั้นก็มีฟอร์มฐานจากรูปแบบอื่นซึ่งได้รับมาซึ่งเรียกว่า Inject บนตัวมันเอง

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

นี่เป็นการบอกวิธีแก้ปัญหาการเดินสายอัตโนมัติเพื่อพยายามฉีดคุณสมบัติทั้งหมดซ้ำในแบบฟอร์มที่เหมาะสมกับกฎที่ตั้งค่าไว้ในโมดูลของคุณ


ทางออกที่ดีมาก ..... ฉันจะลองดู
Pravin Patil

10
นั่นเป็นตัวระบุบริการซึ่งเป็นความคิดที่ไม่ดีอย่างน่าทึ่ง: blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
Mark Seemann

2
@MarkSeemann: Service locator เป็นความคิดที่ไม่ดีถ้าคุณเข้าถึงจากทุกที่แทนที่จะปล่อยให้มันเชื่อมโยงวัตถุระดับบนสุดของคุณลงมาให้ไกลที่สุดเท่าที่จะทำได้ อ่านความคิดเห็นของ Mark ในหน้านี้: "ในกรณีเช่นนี้คุณไม่มีการขอความช่วยเหลือ แต่จะย้าย Composition Root ไปยังแต่ละวัตถุ (เช่นหน้า) และปล่อย DI Container ของคุณขึ้นอยู่กับสิ่งเหล่านี้ รูปแบบการป้องกันของตัวระบุตำแหน่งบริการ แต่ไม่ใช่เพราะคุณยังคงใช้งานคอนเทนเนอร์อย่างน้อยที่สุด " (แก้ไข: รอคุณจะมาร์คดังนั้นสิ่งที่เป็นความแตกต่าง!?)
สาธารณรัฐประชาธิปไตยประชาชนลาว

1
ความแตกต่างคือคุณยังคงสามารถป้องกันส่วนที่เหลือของรหัสฐานของคุณจากนักแต่งเพลงแทนที่จะทำให้ Singleton Service Locator สามารถใช้ได้กับทุกชั้นเรียน
Mark Seemann

2
@pdr: จากประสบการณ์ของฉันหากคุณพยายามที่จะฉีดบริการต่าง ๆ เช่นคลาสแอตทริบิวต์คุณไม่ได้แยกข้อกังวลออกมาอย่างเหมาะสม มีหลายกรณีที่เฟรมเวิร์กที่คุณใช้ทำให้เป็นไปไม่ได้เลยที่จะใช้การฉีดขึ้นต่อกันที่เหมาะสมและบางครั้งเราถูกบังคับให้ใช้ locator ของบริการ แบบแผน
StriplingWarrior

1

การใช้การพึ่งพาการฉีดที่ดีมักอาศัยการแยกรหัสซึ่งสร้างวัตถุและตรรกะทางธุรกิจจริง กล่าวอีกนัยหนึ่งฉันไม่ต้องการให้ทีมของฉันใช้บ่อยnewและสร้างตัวอย่างของคลาสในลักษณะนั้น เมื่อดำเนินการเสร็จแล้วจะไม่มีทางที่จะสลับประเภทที่สร้างขึ้นมาให้เป็นประเภทอื่นได้อย่างง่ายดายเนื่องจากคุณได้ระบุประเภทคอนกรีตไว้แล้ว

ดังนั้นจึงมีสองวิธีในการแก้ไขปัญหานี้:

  1. ฉีดอินสแตนซ์ที่คลาสต้องการ ในตัวอย่างของคุณฉีดTestClassเข้าไปในแบบฟอร์ม Windows ของคุณเพื่อให้มันมีอินสแตนซ์เมื่อมันต้องการ เมื่อ Ninject ยกตัวอย่างแบบฟอร์มของคุณมันจะสร้างการพึ่งพาโดยอัตโนมัติ
  2. ในกรณีที่คุณไม่ต้องการสร้างอินสแตนซ์จนกว่าคุณต้องการคุณสามารถฉีดโรงงานเข้าสู่ตรรกะทางธุรกิจ ตัวอย่างเช่นคุณสามารถฉีดIKernelลงในแบบฟอร์ม Windows TestClassของคุณและจากนั้นใช้ที่อินสแตนซ์ ขึ้นอยู่กับสไตล์ของคุณมีวิธีอื่นที่จะทำให้สำเร็จเช่นกัน (การฉีดคลาสโรงงานผู้แทนโรงงาน ฯลฯ )

การทำเช่นนี้ทำให้ง่ายต่อการสลับทั้งประเภทคอนกรีตของ TestClass เช่นเดียวกับการปรับเปลี่ยนโครงสร้างการก่อสร้างจริงของคลาสการทดสอบโดยไม่ต้องแก้ไขรหัสที่ใช้คลาสการทดสอบจริง


1

ฉันไม่ได้ใช้ Ninject แต่วิธีมาตรฐานในการสร้างสิ่งต่าง ๆ เมื่อคุณใช้ IoC คือการทำผ่านทางFunc<T>ที่Tเป็นประเภทของวัตถุที่คุณต้องการสร้าง ดังนั้นถ้าวัตถุT1ความต้องการที่จะสร้างวัตถุชนิดT2แล้วคอนสตรัคของT1ต้องมีพารามิเตอร์ชนิดFunc<T1>ซึ่งถูกเก็บไว้แล้วเป็นเขตข้อมูล / T2ทรัพย์สินของ ตอนนี้เมื่อคุณต้องการที่จะสร้างวัตถุชนิดT2ในที่คุณเรียกT1Func

นี่เป็นการแยกคุณออกจากกรอบ IoC ของคุณทั้งหมดและเป็นวิธีที่ถูกต้องในการกำหนดรหัสในแนวคิด IoC

ข้อเสียของการทำเช่นนี้คือมันอาจทำให้เกิดความรำคาญเมื่อคุณต้องการ wire Funcs ด้วยตนเองหรือตัวอย่างเมื่อผู้สร้างของคุณต้องการพารามิเตอร์บางอย่างดังนั้น IoC ไม่สามารถตอบรับอัตโนมัติFuncสำหรับคุณ

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.