ตัวสร้างการฉีดคืออะไร?


47

ฉันได้ดูคำว่า constructor injection และการพึ่งพาในขณะที่อ่านบทความเกี่ยวกับรูปแบบการออกแบบ (Service locator)

เมื่อฉันไปเกี่ยวกับการฉีดคอนสตรัคเตอร์ฉันได้ผลลัพธ์ที่ไม่ชัดเจนซึ่งทำให้ฉันเช็คอินที่นี่

ตัวสร้างการฉีดคืออะไร? นี่เป็นการฉีดแบบพึ่งพาหรือไม่? ตัวอย่างที่ยอมรับได้จะเป็นความช่วยเหลือที่ดี!

แก้ไข

กลับมาที่คำถามนี้อีกครั้งหลังจากผ่านไปหนึ่งสัปดาห์ฉันจะเห็นว่าฉันหลงทางได้อย่างไร ... ในกรณีที่มีใครปรากฏขึ้นที่นี่ฉันจะอัปเดตคำถามโดยมีการเรียนรู้เล็กน้อยของฉัน กรุณาอย่าลังเลที่จะแสดงความคิดเห็น / ถูกต้อง คอนสตรัคเตอร์การฉีดและการฉีดคุณสมบัติเป็นสองประเภทของการพึ่งพาการฉีด


5
การเข้าชมครั้งแรกใน Google จะอธิบายอย่างชัดเจน ... misko.hevery.com/2009/02/19/…
user281377

คำตอบ:


95

ฉันไม่มีความเชี่ยวชาญ แต่ฉันคิดว่าฉันสามารถช่วยได้ และใช่มันเป็นประเภทเฉพาะของการพึ่งพาการฉีด

คำเตือน: เกือบทั้งหมดนี้เป็น "ขโมย" จากNinject Wiki

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

class Sword 
{
    public void Hit(string target)
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

จากนั้นให้สร้างคลาสเพื่อเป็นตัวแทนนักรบของเรา เพื่อที่จะโจมตีศัตรูนักรบจะต้องใช้วิธีการโจมตี () เมื่อมีการเรียกวิธีนี้มันควรใช้ Sword เพื่อโจมตีคู่ต่อสู้

class Samurai
{
    readonly Sword sword;
    public Samurai() 
    {
        this.sword = new Sword();
    }

    public void Attack(string target)
    {
        this.sword.Hit(target);
    }
}

ตอนนี้เราสามารถสร้างซามูไรของเราและต่อสู้!

class Program
{
    public static void Main() 
    {
        var warrior = new Samurai();
        warrior.Attack("the evildoers");
    }
}

อย่างที่คุณอาจจินตนาการว่าสิ่งนี้จะพิมพ์Chopped ทำความชั่วในครึ่งไปยังคอนโซล มันใช้งานได้ดี แต่ถ้าเราต้องการที่จะติดอาวุธซามูไรกับอาวุธอื่นล่ะ? เนื่องจากดาบถูกสร้างขึ้นภายในคอนสตรัคเตอร์ของซามูไรเราต้องแก้ไขการใช้งานคลาสเพื่อทำการเปลี่ยนแปลงนี้

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

interface IWeapon
{
    void Hit(string target);
}

จากนั้นคลาส Sword ของเราสามารถใช้ส่วนต่อประสานนี้:

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

และเราสามารถเปลี่ยนชั้นซามูไรของเรา:

class Samurai
{
    readonly IWeapon weapon;
    public Samurai() 
    {
        this.weapon = new Sword();
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

ตอนนี้ซามูไรของเราสามารถติดอาวุธด้วยอาวุธที่แตกต่างกัน แต่เดี๋ยวก่อน! ดาบยังคงถูกสร้างขึ้นภายในตัวสร้างของซามูไร เนื่องจากเรายังคงต้องเปลี่ยนการใช้งานของซามูไรเพื่อให้อาวุธของเรานักรบอีกซามูไรยังคงเป็นคู่กับ Sword อย่างแน่นหนา

โชคดีที่มีทางออกที่ง่าย แทนที่จะสร้างดาบจากภายในตัวสร้างของซามูไรเราสามารถเปิดเผยมันเป็นพารามิเตอร์ของตัวสร้างแทน หรือที่เรียกว่า Constructor Injection

class Samurai
{
    readonly IWeapon weapon;
    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

ขณะที่จอร์โจชี้ให้เห็นยังมีการฉีดคุณสมบัติ นั่นจะเป็นสิ่งที่ชอบ:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


    public void SetWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }

}

หวังว่านี่จะช่วยได้


8
ชอบอันนี้! :) มันอ่านเหมือนเรื่องจริง! แค่สงสัย. หากคุณต้องการแขนซามูไรเดียวกันด้วยอาวุธหลายอย่าง (เรียงตามลำดับ) การฉีดคุณสมบัติจะเป็นวิธีที่ดีที่สุดใช่ไหม
TheSilverBullet

ใช่คุณสามารถทำได้ ที่จริงแล้วฉันคิดว่าคุณน่าจะทำงานได้ดีขึ้นผ่าน IWeapon เป็นพารามิเตอร์ไปยังวิธีการโจมตี เช่นเดียวกับ "โมฆะสาธารณะโจมตี (IWeapon ด้วยเป้าหมายสตริง)" และเพื่อหลีกเลี่ยงรหัสที่ซ้ำกันฉันจะทำให้วิธีการที่มีอยู่ "สาธารณะโมฆะโจมตี (เป้าหมายสตริง)" เหมือนเดิมและทำให้มันเรียกว่า "this.Attack (this.weapon เป้าหมาย)"
Luiz Angelo

จากนั้นเมื่อรวมกับโรงงานคุณจะได้รับวิธีเช่น CreateSwordSamurai ()! ตัวอย่างที่ดี
Geerten

2
เยี่ยมมาก! ชัดเจนแล้ว ...
Serge van den Oever

@Luiz Angelo ขออภัยฉันไม่แน่ใจว่าฉันเข้าใจความคิดเห็นของคุณ: "จริง ๆ แล้วฉันคิดว่าคุณน่าจะได้รับหน้าที่ส่งผ่าน IWeapon ให้เป็นพารามิเตอร์ของวิธีการโจมตีได้ดีขึ้นเช่น .. " คุณหมายถึงโอเวอร์โหลดวิธีการโจมตีในคลาสซามูไร?
Pap

3

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

ลองจินตนาการว่าเรามีคริสตจักรที่เรียกว่ายูบิลลี่ซึ่งมีสาขาทั่วโลก เป้าหมายของเราคือการรับไอเท็มจากแต่ละสาขา นี่จะแก้ปัญหาด้วย DI;

1) สร้างอินเตอร์เฟส IJubilee:

public interface IJubilee
{
    string GetItem(string userInput);
}

2) สร้างคลาส JubileeDI ซึ่งจะใช้อินเทอร์เฟซ IJubilee เป็นตัวสร้างและส่งคืนรายการ:

public class JubileeDI
{
    readonly IJubilee jubilee;

    public JubileeDI(IJubilee jubilee)
    {
        this.jubilee = jubilee;
    }

    public string GetItem(string userInput)
    {
        return this.jubilee.GetItem(userInput);
    }
}

3) ตอนนี้สร้างสามสาขาของ Jubilee คือ JubileeGT, JubileeHOG, JubileeCOV ซึ่งทั้งหมดนี้จะต้องได้รับส่วนต่อประสานของ IJubilee เพื่อความสนุกของมันทำให้หนึ่งในนั้นใช้วิธีการเป็นเสมือน:

public class JubileeGT : IJubilee
{
    public virtual string GetItem(string userInput)
    {
        return string.Format("For JubileeGT, you entered {0}", userInput);
    }
}

public class JubileeHOG : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileeHOG, you entered {0}", userInput);
    }
}

public class JubileeCOV : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileCOV, you entered {0}", userInput);
    }
}

public class JubileeGTBranchA : JubileeGT
{
    public override string GetItem(string userInput)
    {
        return string.Format("For JubileeGT branch A, you entered {0}", userInput);
    }
}

4) นั่นแหละ! ตอนนี้ให้เราเห็น DI ในการดำเนินการ:

        JubileeDI jCOV = new JubileeDI(new JubileeCOV());
        JubileeDI jHOG = new JubileeDI(new JubileeHOG());
        JubileeDI jGT = new JubileeDI(new JubileeGT());
        JubileeDI jGTA = new JubileeDI(new JubileeGTBranchA());

        var item = jCOV.GetItem("Give me some money!");
        var item2 = jHOG.GetItem("Give me a check!");
        var item3 = jGT.GetItem("I need to be fed!!!");
        var item4 = jGTA.GetItem("Thank you!");

สำหรับแต่ละอินสแตนซ์ของคลาสคอนเทนเนอร์เราส่งอินสแตนซ์ใหม่ของคลาสที่เราต้องการ

หวังว่านี่จะอธิบายแนวคิดโดยย่อ


2

สมมติว่าคุณมีชั้นเรียนเช่นกันซึ่งที่ต้องการตัวอย่างของชั้นอื่นAB

class A
{
   B b;
}

การพึ่งพาการฉีดหมายความว่าการอ้างอิงถึงBถูกกำหนดโดยวัตถุที่จัดการอินสแตนซ์ของA(ตรงข้ามกับการให้คลาสที่Aจัดการการอ้างอิงถึงBโดยตรง)

Constructor injection หมายถึงว่าการอ้างอิงถึงBถูกส่งผ่านเป็นพารามิเตอร์ไปยัง Constructor ของAและตั้งค่าใน Constructor:

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

ทางเลือกคือการใช้วิธีการตั้งค่า (หรือสถานที่ให้บริการ) Bเพื่อตั้งค่าการอ้างอิงถึง ในกรณีนี้วัตถุที่จัดการอินสแตนซ์aของAอันดับแรกต้องเรียกตัวสร้างของAและเรียกเมธอด setter ในภายหลังเพื่อตั้งค่าตัวแปรสมาชิกA.bก่อนที่จะaถูกใช้ วิธีการฉีดหลังเป็นสิ่งที่จำเป็นเมื่อวัตถุมีการอ้างอิงวงจรกับแต่ละอื่น ๆ เช่นถ้าเป็นตัวอย่างbของการBที่กำหนดไว้ในอินสแตนซ์aของมีการอ้างอิงกลับไปAa

** แก้ไข **

นี่คือรายละเอียดเพิ่มเติมเพื่อแสดงความคิดเห็น

1. คลาส A จัดการอินสแตนซ์ B

class A
{
    B b;

    A()
    {
        b = new B();
    }
 }

2. B อินสแตนซ์ตั้งอยู่ในตัวสร้าง

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A(b);
}

3. อินสแตนซ์ B ถูกตั้งค่าโดยใช้วิธีการตั้งค่า

class A
{
    B b;

    A()
    {
    }

    void setB(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A();

    a.setB(b);
}

... เมื่อเทียบกับการมีชั้นเรียนการจัดการการอ้างอิงถึง B โดยตรง สิ่งนี้หมายความว่า? คุณจะทำมันอย่างไรในรหัส? (ขออภัยในความไม่รู้ของฉัน)
TheSilverBullet

1
ตัวอย่างซามูไรดีกว่ามาก เราทุกคนหยุดใช้ตัวอักษรสำหรับตัวแปรโปรด ...
stuartdotnet

@ Stuartdotnet: ฉันไม่ได้โต้แย้งเกี่ยวกับตัวอย่างของซามูไรที่ดีกว่าของเลว แต่ทำไมหยุดใช้ตัวแปรหนึ่งตัวอักษร? หากตัวแปรไม่มีความหมายเฉพาะ แต่เป็นเพียงตัวยึดตำแหน่งตัวแปรตัวอักษรเดียวจะมีขนาดเล็กลงและตรงประเด็นมากขึ้น การใช้ชื่อที่ยาวขึ้นเช่นitemAหรือobjectAเพียงเพื่อทำให้ชื่อยาวขึ้นนั้นไม่ได้ดีกว่าเสมอไป
Giorgio

1
จากนั้นคุณพลาดจุดของฉัน - การใช้ชื่อที่ไม่อธิบายเป็นการยากที่จะอ่านติดตามและทำความเข้าใจและไม่ใช่วิธีที่ดีในการอธิบายประเด็น
stuartdotnet

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