ใครสามารถอธิบาย Microsoft Unity ได้บ้าง


157

ฉันได้อ่านบทความเกี่ยวกับ MSDN เกี่ยวกับ Unity (Dependency Injection, Inversion of Control) แต่ฉันคิดว่าฉันต้องการมันอธิบายในคำศัพท์ง่ายๆ (หรือตัวอย่างง่ายๆ) ฉันคุ้นเคยกับรูปแบบ MVPC (เราใช้ที่นี่) แต่ฉันก็ยังไม่เข้าใจสิ่งที่เป็นเอกภาพจริงๆและฉันคิดว่ามันเป็นขั้นตอนต่อไปในการออกแบบแอปพลิเคชันของเรา


12
ฉันชอบที่ชื่อนี้มีชื่อว่า "Unity" ดังนั้นเมื่อฉันค้นหา Unity Game Engine ฉันเห็นเทคโนโลยีเก่า ๆ ถอนหายใจ ฉันเดาชื่อวงที่ดีทั้งหมดแล้ว
Tom Schulz

2
@ tom-schulz เทคโนโลยีเก่าหรือไม่ nuget.org/packages/Unity - อัปเดตล่าสุด 5 วันที่ผ่านมา
Roger Willcocks

คำตอบ:


174

Unity เป็นเพียง IoC "container" Google โครงสร้างแผนที่และลองใช้แทน ฉันคิดว่าเรื่องง่ายกว่านี้ถ้าฉันเป็นคนใหม่สำหรับคุณ

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

โดยไม่มี IoC:

public class MyClass
{
   IMyService _myService; 

   public MyClass()
   {
      _myService = new SomeConcreteService();    
   }
}

ด้วยคอนเทนเนอร์ IoC:

public class MyClass
{
   IMyService _myService; 

   public MyClass(IMyService myService)
   {
      _myService = myService;    
   }
}

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

ด้วยคอนเทนเนอร์ IoC คุณ "กำหนดค่า" คอนเทนเนอร์เพื่อแก้ไขการอ้างอิงเหล่านั้นสำหรับคุณ ดังนั้นด้วยรูปแบบการฉีดขึ้นกับคอนสตรัคเตอร์คุณเพียงแค่ส่งอินเทอร์เฟซไปยังการพึ่งพา IMyService ในตัวสร้าง เมื่อคุณสร้าง MyClass ด้วยคอนเทนเนอร์ของคุณคอนเทนเนอร์ของคุณจะแก้ไขการพึ่งพา IMyService สำหรับคุณ

ใช้ StructureMap การกำหนดค่าคอนเทนเนอร์มีลักษณะดังนี้:

StructureMapConfiguration.ForRequestedType<MyClass>().TheDefaultIsConcreteType<MyClass>();
StructureMapConfiguration.ForRequestedType<IMyService>().TheDefaultIsConcreteType<SomeConcreteService>();

ดังนั้นสิ่งที่คุณทำจะถูกบอกกับคอนเทนเนอร์ว่า "เมื่อมีคนร้องขอ IMyService ให้สำเนาของ SomeConcreteService" แก่พวกเขา และคุณได้ระบุด้วยว่าเมื่อมีคนขอ MyClass พวกเขาจะได้รับ MyClass อย่างเป็นรูปธรรม

นั่นคือคอนเทนเนอร์ IoC ทั้งหมดใช้งานได้จริง พวกเขาสามารถทำอะไรได้มากกว่า แต่นั่นคือแรงผลักดันของมัน - พวกเขาแก้ไขการพึ่งพาสำหรับคุณดังนั้นคุณไม่จำเป็นต้อง (และคุณไม่จำเป็นต้องใช้คำหลัก "ใหม่" ตลอดรหัสของคุณ)

ขั้นตอนสุดท้าย: เมื่อคุณสร้าง MyClass ของคุณคุณจะทำสิ่งนี้:

var myClass = ObjectFactory.GetInstance<MyClass>();

หวังว่าจะช่วย โปรดส่งอีเมลถึงฉัน


2
ดังนั้นฉันจึงคิดว่าเป็นโรงงาน หากฉันติดตามอย่างถูกต้องคุณจะไม่ใช้ <IMyClass> แทน <MyClass> ในตัวอย่างสุดท้ายหรือไม่ ดังนั้นมันจะเป็น var myClass = ObjectFactory.GetInstance <IMyClass> ()? ขอบคุณสำหรับความช่วยเหลือของคุณนี่เป็นการเริ่มต้นที่ดีสำหรับฉัน!
Ryan Abbott

3
มันเป็นเหมือนโรงงานใช่ไหม โรงงานต้นแบบสำหรับแอปพลิเคชันของคุณ แต่สามารถกำหนดค่าให้ส่งคืนประเภทที่แตกต่างจำนวนมากรวมถึงซิงเกิลตัน สำหรับอินเทอร์เฟซสำหรับ MyClass - ถ้าเป็นวัตถุทางธุรกิจฉันจะไม่แยกอินเทอร์เฟซ สำหรับทุกอย่างอื่นฉันมักจะ
Chris Holmes

เกิดอะไรขึ้นถ้าคุณเรียกว่า ObjectFactory.GetInstance <MyClass> (); และคุณไม่ได้กำหนดค่า SomeConcreteClass คุณจะได้รับและเกิดข้อผิดพลาดในกรณีนั้นหรือไม่?
RayLoveless

1
@ เรย์: มันขึ้นอยู่กับภาชนะ คอนเทนเนอร์บางตัวถูกเขียนโดยค่าเริ่มต้นจะใช้หลักการตั้งชื่อเช่นถ้าคลาสชื่อ MyClass และอินเตอร์เฟสชื่อ IMyInterface คอนเทนเนอร์จะกำหนดค่าคลาสนั้นสำหรับอินเตอร์เฟสนั้นโดยอัตโนมัติ ดังนั้นในกรณีนี้หากคุณไม่ได้กำหนดค่าด้วยตนเอง "แบบแผน" เริ่มต้นของคอนเทนเนอร์จะหยิบขึ้นมาใหม่ อย่างไรก็ตามหากคลาสและส่วนต่อประสานของคุณไม่เป็นไปตามแบบแผนและคุณไม่ได้กำหนดค่าคอนเทนเนอร์สำหรับคลาสนั้นคุณก็จะได้รับข้อผิดพลาดขณะใช้งานจริง
Chris Holmes

1
@saravanan ฉันคิดว่า StructureMap ทำตามแบบแผนชื่อตอนนี้ ฉันไม่แน่ใจ เราไม่ได้ใช้มันมานาน (ฉันเขียนแบบกำหนดเองสำหรับธุรกิจของเราใช้แบบแผนการชื่อเดียวกันสำหรับอินเทอร์เฟซและคลาส)
Chris Holmes

39

ฉันเพิ่งดูการฉีดขึ้นรูป IoC Screencast ของ Unity Dependency 30 นาทีโดย David Hayden และรู้สึกว่าเป็นการอธิบายที่ดีพร้อมตัวอย่าง นี่คือตัวอย่างจากบันทึกการแสดง:

Screencast แสดงให้เห็นถึงการใช้งานทั่วไปของ Unity IoC เช่น:

  • การสร้างประเภทไม่ได้อยู่ในคอนเทนเนอร์
  • การลงทะเบียนและแก้ไข TypeMappings
  • การลงทะเบียนและแก้ไข TypeMappings ที่มีชื่อ
  • Singletons, LifetimeManagers และ ContainerControlledLifetimeManager
  • การลงทะเบียนอินสแตนซ์ที่มีอยู่
  • การฉีดการพึ่งพาลงในอินสแตนซ์ที่มีอยู่
  • การเติมข้อมูล UnityContainer ผ่าน App.config / Web.config
  • การระบุการพึ่งพาผ่านการฉีด API ซึ่งต่างจากแอตทริบิวต์การพึ่งพา
  • การใช้ภาชนะบรรจุที่ซ้อนกัน (แม่ลูก)

32

Unity เป็นไลบรารีที่เหมือนกับหลาย ๆ ประเภทที่ให้คุณได้รับอินสแตนซ์ของประเภทที่ร้องขอโดยไม่ต้องสร้างมันเอง ได้รับดังนั้น

public interface ICalculator
{
    void Add(int a, int b);
}

public class Calculator : ICalculator
{
    public void Add(int a, int b)
    {
        return a + b;
    }
}

คุณจะใช้ไลบรารีเช่น Unity เพื่อลงทะเบียนเครื่องคิดเลขที่จะส่งคืนเมื่อมีการร้องขอประเภท ICalculator หรือ IoC (Inversion of Control) (ตัวอย่างนี้เป็นทฤษฎีไม่ถูกต้องทางเทคนิค)

IoCLlibrary.Register<ICalculator>.Return<Calculator>();

ดังนั้นเมื่อคุณต้องการอินสแตนซ์ของ ICalculator คุณเพียงแค่ ...

Calculator calc = IoCLibrary.Resolve<ICalculator>();

โดยทั่วไปแล้วไลบรารี IoC สามารถกำหนดค่าให้เก็บซิงเกิลตันหรือสร้างอินสแตนซ์ใหม่ทุกครั้งที่คุณแก้ไขประเภท

ตอนนี้สมมติว่าคุณมีคลาสที่อาศัย ICalculator เป็นปัจจุบันคุณอาจมี ..

public class BankingSystem
{
    public BankingSystem(ICalculator calc)
    {
        _calc = calc;
    }

    private ICalculator _calc;
}

และคุณสามารถตั้งค่าไลบรารี่ให้ฉีดวัตถุเข้าไปในคอนสตรัคเตอร์เมื่อมันถูกสร้างขึ้น

ดังนั้น DI หรือ Dependency Injection หมายถึงการฉีดวัตถุใด ๆ ที่อาจต้องการ


ควรเป็น ICalculator calc = IoCLibrary.Resolve <ICalculator> ();
Shukhrat Raimov

31

ผู้ชายคนนี้ WilcoxTutorials ให้การสาธิตที่ยอดเยี่ยมของ Unity container ที่มุ่งเป้าไปที่ผู้เริ่มต้น

ส่วนที่ 1: http://www.youtube.com/watch?v=CWwe9Z0Gyew

ส่วนที่ 2: http://www.youtube.com/watch?v=PsIbevgzQQE

ในเวลาน้อยกว่าครึ่งชั่วโมงและคุณจะเข้าใจพื้นฐาน!


3
นั่นเป็น vids ที่เป็นประโยชน์จริงๆ
gdubs

10

ความสามัคคีคือ IoC จุดประสงค์ของ IoC คือการสรุปการเดินสายของการพึ่งพาระหว่างชนิดที่อยู่นอกประเภทของตัวเอง นี่เป็นข้อดีสองสามข้อ ก่อนอื่นจะทำจากส่วนกลางซึ่งหมายความว่าคุณไม่จำเป็นต้องเปลี่ยนรหัสจำนวนมากเมื่อมีการเปลี่ยนแปลงการอ้างอิง (ซึ่งอาจเป็นกรณีสำหรับการทดสอบหน่วย)

นอกจากนี้หากการเดินสายเสร็จสิ้นโดยใช้ข้อมูลการกำหนดค่าแทนที่จะเป็นรหัสคุณสามารถเชื่อมโยงการอ้างอิงหลังจากการปรับใช้จริงและเปลี่ยนพฤติกรรมของแอปพลิเคชันโดยไม่ต้องเปลี่ยนรหัส


5

MSDN มีคำแนะนำของนักพัฒนาเพื่อการฉีดพึ่งพาโดยใช้ Unityที่อาจเป็นประโยชน์

คู่มือนักพัฒนาซอฟต์แวร์เริ่มต้นด้วยพื้นฐานของการฉีดแบบพึ่งพาและทำต่อไปด้วยตัวอย่างวิธีใช้ Unity สำหรับการฉีดแบบพึ่งพา ตั้งแต่เดือนกุมภาพันธ์ 2014 คู่มือนักพัฒนาซอฟต์แวร์ครอบคลุม Unity 3.0 ซึ่งเผยแพร่ในเดือนเมษายน 2556


1

ฉันครอบคลุมตัวอย่างส่วนใหญ่ของการพึ่งพาการฉีดใน ASP.NET Web API 2

public interface IShape
{
    string Name { get; set; }
}

public class NoShape : IShape
{
    public string Name { get; set; } = "I have No Shape";
}

public class Circle : IShape
{
    public string Name { get; set; } = "Circle";
}

public class Rectangle : IShape
{
    public Rectangle(string name)
    {
        this.Name = name;
    }

    public string Name { get; set; } = "Rectangle";
}

ใน DIAutoV2Controller.cs ใช้กลไกการฉีดอัตโนมัติ

[RoutePrefix("api/v2/DIAutoExample")]
public class DIAutoV2Controller : ApiController
{
    private string ConstructorInjected;
    private string MethodInjected1;
    private string MethodInjected2;
    private string MethodInjected3;

    [Dependency]
    public IShape NoShape { get; set; }

    [Dependency("Circle")]
    public IShape ShapeCircle { get; set; }

    [Dependency("Rectangle")]
    public IShape ShapeRectangle { get; set; }

    [Dependency("PiValueExample1")]
    public double PiValue { get; set; }

    [InjectionConstructor]
    public DIAutoV2Controller([Dependency("Circle")]IShape shape1, [Dependency("Rectangle")]IShape shape2, IShape shape3)
    {
        this.ConstructorInjected = shape1.Name + " & " + shape2.Name + " & " + shape3.Name;
    }

    [NonAction]
    [InjectionMethod]
    public void Initialize()
    {
        this.MethodInjected1 = "Default Initialize done";
    }

    [NonAction]
    [InjectionMethod]
    public void Initialize2([Dependency("Circle")]IShape shape1)
    {
        this.MethodInjected2 = shape1.Name;
    }

    [NonAction]
    [InjectionMethod]
    public void Initialize3(IShape shape1)
    {
        this.MethodInjected3 = shape1.Name;
    }

    [HttpGet]
    [Route("constructorinjection")]
    public string constructorinjection()
    {
        return "Constructor Injected: " + this.ConstructorInjected;
    }

    [HttpGet]
    [Route("GetNoShape")]
    public string GetNoShape()
    {
        return "Property Injected: " + this.NoShape.Name;
    }

    [HttpGet]
    [Route("GetShapeCircle")]
    public string GetShapeCircle()
    {
        return "Property Injected: " + this.ShapeCircle.Name;
    }

    [HttpGet]
    [Route("GetShapeRectangle")]
    public string GetShapeRectangle()
    {
        return "Property Injected: " + this.ShapeRectangle.Name;
    }

    [HttpGet]
    [Route("GetPiValue")]
    public string GetPiValue()
    {
        return "Property Injected: " + this.PiValue;
    }

    [HttpGet]
    [Route("MethodInjected1")]
    public string InjectionMethod1()
    {
        return "Method Injected: " + this.MethodInjected1;
    }

    [HttpGet]
    [Route("MethodInjected2")]
    public string InjectionMethod2()
    {
        return "Method Injected: " + this.MethodInjected2;
    }

    [HttpGet]
    [Route("MethodInjected3")]
    public string InjectionMethod3()
    {
        return "Method Injected: " + this.MethodInjected3;
    }
}

ใน DIV2Controller.cs ทุกอย่างจะถูกฉีดจากคลาส Dependency Resolver Configuration

[RoutePrefix("api/v2/DIExample")]
public class DIV2Controller : ApiController
{
    private string ConstructorInjected;
    private string MethodInjected1;
    private string MethodInjected2;
    public string MyPropertyName { get; set; }
    public double PiValue1 { get; set; }
    public double PiValue2 { get; set; }
    public IShape Shape { get; set; }

    // MethodInjected
    [NonAction]
    public void Initialize()
    {
        this.MethodInjected1 = "Default Initialize done";
    }

    // MethodInjected
    [NonAction]
    public void Initialize2(string myproperty1, IShape shape1, string myproperty2, IShape shape2)
    {
        this.MethodInjected2 = myproperty1 + " & " + shape1.Name + " & " + myproperty2 + " & " + shape2.Name;
    }

    public DIV2Controller(string myproperty1, IShape shape1, string myproperty2, IShape shape2)
    {
        this.ConstructorInjected = myproperty1 + " & " + shape1.Name + " & " + myproperty2 + " & " + shape2.Name;
    }

    [HttpGet]
    [Route("constructorinjection")]
    public string constructorinjection()
    {
        return "Constructor Injected: " + this.ConstructorInjected;
    }

    [HttpGet]
    [Route("PropertyInjected")]
    public string InjectionProperty()
    {
        return "Property Injected: " + this.MyPropertyName;
    }

    [HttpGet]
    [Route("GetPiValue1")]
    public string GetPiValue1()
    {
        return "Property Injected: " + this.PiValue1;
    }

    [HttpGet]
    [Route("GetPiValue2")]
    public string GetPiValue2()
    {
        return "Property Injected: " + this.PiValue2;
    }

    [HttpGet]
    [Route("GetShape")]
    public string GetShape()
    {
        return "Property Injected: " + this.Shape.Name;
    }

    [HttpGet]
    [Route("MethodInjected1")]
    public string InjectionMethod1()
    {
        return "Method Injected: " + this.MethodInjected1;
    }

    [HttpGet]
    [Route("MethodInjected2")]
    public string InjectionMethod2()
    {
        return "Method Injected: " + this.MethodInjected2;
    }
}

การกำหนดค่า Dependency Resolver

public static void Register(HttpConfiguration config)
{
    var container = new UnityContainer();
    RegisterInterfaces(container);
    config.DependencyResolver = new UnityResolver(container);

    // Other Web API configuration not shown.
}

private static void RegisterInterfaces(UnityContainer container)
{
    var dbContext = new SchoolDbContext();
    // Registration with constructor injection
    container.RegisterType<IStudentRepository, StudentRepository>(new InjectionConstructor(dbContext));
    container.RegisterType<ICourseRepository, CourseRepository>(new InjectionConstructor(dbContext));

    // Set constant/default value of Pi = 3.141 
    container.RegisterInstance<double>("PiValueExample1", 3.141);
    container.RegisterInstance<double>("PiValueExample2", 3.14);

    // without a name
    container.RegisterInstance<IShape>(new NoShape());

    // with circle name
    container.RegisterType<IShape, Circle>("Circle", new InjectionProperty("Name", "I am Circle"));

    // with rectangle name
    container.RegisterType<IShape, Rectangle>("Rectangle", new InjectionConstructor("I am Rectangle"));

    // Complex type like Constructor, Property and method injection
    container.RegisterType<DIV2Controller, DIV2Controller>(
        new InjectionConstructor("Constructor Value1", container.Resolve<IShape>("Circle"), "Constructor Value2", container.Resolve<IShape>()),
        new InjectionMethod("Initialize"),
        new InjectionMethod("Initialize2", "Value1", container.Resolve<IShape>("Circle"), "Value2", container.Resolve<IShape>()),
        new InjectionProperty("MyPropertyName", "Property Value"),
        new InjectionProperty("PiValue1", container.Resolve<double>("PiValueExample1")),
        new InjectionProperty("Shape", container.Resolve<IShape>("Rectangle")),
        new InjectionProperty("PiValue2", container.Resolve<double>("PiValueExample2")));
}

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