ทำความเข้าใจกับการฉีดพึ่งพา


109

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

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

สำหรับฉันนี่เป็นเพียงการโต้แย้ง ฉันต้องคิดถึงจุดที่เข้าใจ บางทีมันอาจจะชัดเจนมากขึ้นกับโครงการขนาดใหญ่?

ความเข้าใจของฉันไม่ใช่ -Di (ใช้รหัสเทียม):

public void Start()
{
    MyClass class = new MyClass();
}

...

public MyClass()
{
    this.MyInterface = new MyInterface(); 
}

และ DI ก็จะเป็น

public void Start()
{
    MyInterface myInterface = new MyInterface();
    MyClass class = new MyClass(myInterface);
}

...

public MyClass(MyInterface myInterface)
{
    this.MyInterface = myInterface; 
}

ใครบางคนสามารถหลั่งน้ำตาแสงบางอย่างฉันแน่ใจว่าฉันอยู่ในความยุ่งเหยิงที่นี่


5
ลองมี 5 ของ MyInterface และ MyClass และมีหลายคลาสที่สร้างห่วงโซ่การพึ่งพา มันจะกลายเป็นความเจ็บปวดในก้นเพื่อตั้งค่าทุกอย่าง
ร่าเริง

159
" สำหรับฉันนี่เป็นเพียงการทะเลาะโต้เถียงกันฉันต้องเข้าใจจุดผิดหรือเปล่า? " ไม่เลย คุณได้รับมัน นั่นคือ "การฉีดพึ่งพา" ตอนนี้มาดูกันว่าศัพท์แสงบ้าอื่น ๆ ที่คุณสามารถสร้างขึ้นมาสำหรับแนวคิดง่ายๆมันสนุก!
Eric Lippert

8
ฉันไม่สามารถโหวตความคิดเห็นของ Mr Lippert ได้มากพอ ฉันก็พบว่ามันจะสับสนกับศัพท์แสงสำหรับสิ่งที่ฉันทำตามธรรมชาติอยู่แล้ว
Alex Humphrey

16
@EricLippert: ในขณะที่ถูกต้องฉันคิดว่านั่นลดลงเล็กน้อย พารามิเตอร์-as-DI ไม่ได้เป็นแนวคิดที่ตรงไปตรงมาสำหรับโปรแกรมเมอร์ OO ที่มีความจำเป็นโดยเฉลี่ยซึ่งเคยผ่านการส่งข้อมูลไปมา DI ที่นี่จะช่วยให้คุณผ่านพฤติกรรมไปมาได้ซึ่งต้องการมุมมองที่ยืดหยุ่นกว่านักเขียนโปรแกรมธรรมดา
Phoshi

14
@Phoshi: คุณทำคะแนนได้ดี แต่คุณก็ยังเอาเป็นว่าทำไมฉันถึงสงสัยว่า "การฉีดพึ่งพา" เป็นความคิดที่ดีตั้งแต่แรก ถ้าฉันเขียนคลาสที่ขึ้นอยู่กับความถูกต้องและประสิทธิภาพของมันในพฤติกรรมของคลาสอื่นสิ่งสุดท้ายที่ฉันต้องการคือทำให้ผู้ใช้ของคลาสรับผิดชอบในการสร้างและกำหนดค่าการพึ่งพาอย่างถูกต้อง! นั่นคืองานของฉัน ทุกครั้งที่คุณปล่อยให้ผู้บริโภคฉีดการพึ่งพาคุณสร้างจุดที่ผู้บริโภคอาจทำผิด
Eric Lippert

คำตอบ:


106

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

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


13
นี่เป็นคำอธิบายที่ดีมาก โปรดยอมรับ +1 ในจินตนาการเนื่องจากตัวแทนของฉันยังไม่อนุญาต!
MyDaftQuestions

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

1
@ MattGibson จุดที่ดี
Stefan Billiet

2
+1 สำหรับการทดสอบ DI ที่มีโครงสร้างที่ดีจะช่วยให้ทั้งการทดสอบและการทดสอบหน่วยภายในคลาสที่คุณทำการทดสอบนั้นรวดเร็วขึ้น
Umur Kontacı

52

วิธีที่ DI สามารถนำไปใช้นั้นขึ้นอยู่กับภาษาที่ใช้

นี่เป็นตัวอย่างที่ไม่ง่าย:

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo() {
        bar = new Bar();
        qux = new Qux();
    }
}

barนี้ครับเช่นเมื่อสำหรับการทดสอบผมต้องการที่จะใช้วัตถุจำลองสำหรับ ดังนั้นเราสามารถทำให้มีความยืดหยุ่นมากขึ้นและอนุญาตให้ส่งผ่านอินสแตนซ์ของ Constructor ได้:

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo(Bar bar, Qux qux) {
        this.bar = bar;
        this.qux = qux;
    }
}

// in production:
new Foo(new Bar(), new Qux());
// in test:
new Foo(new BarMock(), new Qux());

นี่เป็นรูปแบบการฉีดที่ง่ายที่สุดแล้ว แต่สิ่งนี้ยังคงแย่อยู่เพราะทุกสิ่งต้องทำด้วยตนเอง (เช่นกันเพราะผู้โทรสามารถเก็บการอ้างอิงไปยังวัตถุภายในของเราและทำให้สถานะของเราเป็นโมฆะ)

เราสามารถแนะนำสิ่งที่เป็นนามธรรมมากขึ้นโดยใช้โรงงาน:

  • ทางเลือกหนึ่งสำหรับการFooสร้างโดยโรงงานนามธรรม

    interface FooFactory {
        public Foo makeFoo();
    }
    
    class ProductionFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new Bar(), new Baz()) }
    }
    
    class TestFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new BarMock(), new Baz()) }
    }
    
    FooFactory fac = ...; // depends on test or production
    Foo foo = fac.makeFoo();
  • ตัวเลือกอื่นคือการส่งโรงงานไปยังตัวสร้าง:

    interface DependencyManager {
        public Bar makeBar();
        public Qux makeQux();
    }
    class ProductionDM implements DependencyManager {
        public Bar makeBar() { return new Bar() }
        public Qux makeQux() { return new Qux() }
    }
    class TestDM implements DependencyManager {
        public Bar makeBar() { return new BarMock() }
        public Qux makeQux() { return new Qux() }
    }
    
    class Foo {
        private Bar bar;
        private Qux qux;
    
        public Foo(DependencyManager dm) {
            bar = dm.makeBar();
            qux = dm.makeQux();
        }
    }

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

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

package Foo {
    use signatures;

    sub new($class, $dm) {
        return bless {
            bar => $dm->{bar}->new,
            qux => $dm->{qux}->new,
        } => $class;
    }
}

my $prod = { bar => 'My::Bar', qux => 'My::Qux' };
my $test = { bar => 'BarMock', qux => 'QuxMock' };
$test->{bar} = 'OtherBarMock';  # change conf at runtime

my $foo = Foo->new(rand > 0.5 ? $prod : $test);

ในภาษาอย่าง Java ฉันสามารถให้ตัวจัดการการพึ่งพาทำงานคล้ายกับMap<Class, Object>:

Bar bar = dm.make(Bar.class);

คลาสที่แท้จริงได้รับการBar.classแก้ไขสามารถกำหนดค่าที่รันไทม์เช่นโดยการรักษาMap<Class, Class>อินเทอร์เฟซที่แมปไปยังการใช้งาน

Map<Class, Class> dependencies = ...;

public <T> T make(Class<T> c) throws ... {
    // plus a lot more error checking...
    return dependencies.get(c).newInstance();
}

ยังคงมีองค์ประกอบแบบแมนนวลที่เกี่ยวข้องในการเขียนนวกรรมิก แต่เราสามารถสร้างนวกรรมิกได้โดยไม่จำเป็นเช่นการขับ DI ผ่านคำอธิบายประกอบ:

class Foo {
    @Inject(Bar.class)
    private Bar bar;

    @Inject(Qux.class)
    private Qux qux;

    ...
}

dm.make(Foo.class);  // takes care of initializing "bar" and "qux"

นี่คือตัวอย่างการใช้งานกรอบเล็ก ๆ DI (และถูก จำกัด มาก): http://ideone.com/b2ubuFแม้ว่าการใช้งานนี้จะไม่สามารถใช้ได้อย่างสมบูรณ์สำหรับวัตถุที่ไม่เปลี่ยนรูป (การใช้งานแบบไร้เดียงสานี้ไม่สามารถรับพารามิเตอร์ใด ๆ


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

33
มันสนุกที่การทำให้เข้าใจง่ายขึ้นในแต่ละครั้งมีความซับซ้อนมากขึ้น
Kromster

48

เพื่อนของเราในกองมากเกินมีคำตอบที่ดีนี้ สิ่งที่ฉันชอบคือคำตอบที่สองรวมถึงข้อความอ้างอิง:

"การพึ่งพาการฉีด" เป็นคำที่ 25 ดอลลาร์สำหรับแนวคิดร้อยละ 5 (... ) การฉีดพึ่งพาหมายถึงการให้วัตถุตัวแปรอินสแตนซ์ของมัน ( ... )

จากบล็อกเจมส์ฝั่งของ แน่นอนว่ามีเวอร์ชัน / รูปแบบที่ซับซ้อนมากขึ้นอยู่ด้านบนของสิ่งนี้ แต่ก็เพียงพอที่จะเข้าใจโดยทั่วไปว่าเกิดอะไรขึ้น แทนที่จะเป็นวัตถุที่สร้างตัวแปรอินสแตนซ์ของตัวเองพวกมันจะถูกส่งจากภายนอก


10
ฉันได้อ่านสิ่งนี้ ... และจนถึงตอนนี้มันไม่สมเหตุสมผล ... ตอนนี้มันสมเหตุสมผลแล้ว การพึ่งพาไม่ได้จริง ๆ แล้วเป็นแนวคิดที่จะเข้าใจ (ไม่ว่ามันจะทรงพลังแค่ไหน) แต่มันก็มีชื่อใหญ่และเสียงรบกวนทางอินเทอร์เน็ตมากมายที่เกี่ยวข้อง!
MyDaftQuestions

18

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

แนวคิดเบื้องหลัง DI คือการใช้วิธีการ "ปลั๊กอิน" ทุกที่ สิ่งนี้อาจดูเหมือนไม่ใช่เรื่องใหญ่หากคุณอาศัยอยู่ในกระท่อมเรียบง่ายที่ไม่มีผนังและสายไฟฟ้าทั้งหมดสัมผัส (คุณเป็นช่างซ่อมบำรุง - คุณสามารถเปลี่ยนแปลงอะไรก็ได้!) และในทำนองเดียวกันในแอปพลิเคชั่นขนาดเล็กและเรียบง่าย DI สามารถเพิ่มความยุ่งยากได้มากกว่าที่คุ้มค่า

แต่สำหรับแอพพลิเคชั่นขนาดใหญ่และซับซ้อน DI นั้นขาดไม่ได้สำหรับการบำรุงรักษาในระยะยาว จะช่วยให้คุณ "ถอดปลั๊ก" โมดูลทั้งหมดจากรหัสของคุณ (เช่นแบ็กเอนด์ฐานข้อมูล) และแลกเปลี่ยนกับโมดูลอื่นที่ทำสิ่งเดียวกัน แต่ทำในวิธีที่แตกต่างอย่างสิ้นเชิง (เช่นระบบจัดเก็บบนคลาวด์)

BTW การสนทนาของ DI จะไม่สมบูรณ์หากไม่มีคำแนะนำของDependency Injection ใน. NETโดย Mark Seeman หากคุณคุ้นเคยกับ. NET และคุณตั้งใจจะมีส่วนร่วมในการพัฒนา SW ขนาดใหญ่นี่เป็นการอ่านที่จำเป็น เขาอธิบายแนวคิดได้ดีกว่าที่ฉันสามารถทำได้

ให้ฉันทิ้งคุณไว้ด้วยคุณสมบัติที่สำคัญอย่างหนึ่งของ DI ที่ตัวอย่างโค้ดของคุณมองเห็น DI ช่วยให้คุณสามารถควบคุมความยืดหยุ่นที่มีขนาดใหญ่ที่ห่อหุ้มอยู่ในสุภาษิต " โปรแกรมเพื่อส่วนต่อประสาน " ในการปรับเปลี่ยนตัวอย่างเล็กน้อย:

public void Main()
{
    ILightFixture fixture = new ClassyChandelier();
    MyRoom room = new MyRoom (fixture);
}
...
public MyRoom(ILightFixture fixture)
{
    this.MyLightFixture = fixture ; 
}

แต่ตอนนี้เนื่องจากMyRoomได้รับการออกแบบมาเพื่ออินเทอร์เฟซ ILightFixture ฉันสามารถไปได้อย่างง่ายดายในปีหน้าและเปลี่ยนหนึ่งบรรทัดในฟังก์ชั่นหลัก ("ฉีด" การพึ่งพา "ที่แตกต่างกัน") และฉันได้รับฟังก์ชั่นใหม่ใน MyRoom ทันที สร้างใหม่หรือปรับใช้ MyRoom อีกครั้งหากเกิดขึ้นในห้องสมุดที่แยกต่างหากแน่นอนว่าการใช้งาน ILightFixture ทั้งหมดของคุณได้รับการออกแบบโดยคำนึงถึงการทดแทน Liskov) ทั้งหมดนี้มีการเปลี่ยนแปลงง่าย ๆ :

public void Main()
{
    ILightFixture fixture = new MuchLessFormalLightFixture();
    MyRoom room = new MyRoom (fixture);
}

ยิ่งกว่านั้นตอนนี้ฉันสามารถทดสอบหน่วยMyRoom(คุณคือการทดสอบหน่วยใช่มั้ย) และใช้อุปกรณ์ติดตั้งแบบ "จำลอง" ซึ่งทำให้ฉันสามารถทดสอบได้MyRoomโดยอิสระClassyChandelier.

มีข้อดีอีกมากมายแน่นอน แต่สิ่งเหล่านี้เป็นแนวคิดที่ขายความคิดให้ฉัน


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

2
ฉันสนใจหนังสือที่คุณอ้างอิงด้วย แต่ฉันคิดว่ามันน่าตกใจที่มีหนังสือ 584 หน้าในหัวข้อนี้
Steve

4

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

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

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

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

public void Start()
{
    MySubSubInterfaceA mySubSubInterfaceA = new mySubSubInterfaceA();
    MySubSubInterfaceB mySubSubInterfaceB = new mySubSubInterfaceB();     
    MySubInterface mySubInterface = new MySubInterface(mySubSubInterfaceA,mySubSubInterfaceB);
    MyInterface myInterface = new MyInterface(MySubInterface);
    MyClass class = new MyClass(myInterface);
}

แต่มีอีก 400 บรรทัดของรหัสโลดโผนชนิดนี้ หากคุณนึกภาพว่าเมื่อเวลาผ่านไปความดึงดูดใจของตู้คอนเทนเนอร์ DI จะชัดเจนขึ้น

ลองจินตนาการถึงชั้นเรียนที่ใช้ IDisposable หากชั้นเรียนที่ใช้มันรับมันผ่านการฉีดมากกว่าจะสร้างมันขึ้นมาเองพวกเขาจะรู้ได้อย่างไรว่าเมื่อไหร่ที่จะเรียก Dispose ()? (ซึ่งเป็นอีกปัญหาหนึ่งที่ DI คอนเทนเนอร์เกี่ยวข้อง)

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


นั่นเป็นจำนวนมากและส่วนย่อย! คุณหมายถึงการสร้างอินเทอร์เฟซใหม่แทนคลาสที่ใช้อินเทอร์เฟซหรือไม่ ดูเหมือนผิด
JBRWilkinson

@JBRWilkinson ฉันหมายถึงคลาสที่ใช้อินเทอร์เฟซ ฉันควรทำให้ชัดเจนยิ่งขึ้น อย่างไรก็ตามประเด็นก็คือแอปขนาดใหญ่จะมีคลาสที่มีการพึ่งพาซึ่งในทางกลับกันจะมีการพึ่งพาที่จะมีการพึ่งพา ... การตั้งค่าทั้งหมดด้วยตนเองในวิธีการหลักเป็นผลมาจากการฉีดคอนสตรัคจำนวนมาก
psr

4

ในระดับชั้นเรียนเป็นเรื่องง่าย

'การพึ่งพาการฉีด' เพียงตอบคำถาม "ฉันจะหาผู้ทำงานร่วมกันของฉันได้อย่างไร" ด้วย "พวกเขาจะถูกผลักคุณ - คุณไม่จำเป็นต้องไปหาเขาเอง" (คล้ายกับ - แต่ไม่เหมือนกับ - 'Inversion of Control' โดยที่คำถาม "ฉันจะสั่งการดำเนินการของฉันกับอินพุตของฉันได้อย่างไร" มีคำตอบที่คล้ายกัน)

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

(ผลประโยชน์อื่น ๆ ทั้งหมดของความสามารถในการทดสอบการมีเพศสัมพันธ์แบบหลวม ฯลฯ ตามมาจากการใช้อินเทอร์เฟซส่วนใหญ่และไม่มากจากการพึ่งพาการฉีดแบบพึ่งพาแม้ว่า DI จะส่งเสริมการใช้อินเทอร์เฟซตามธรรมชาติ)

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

และนั่นเป็นสิ่งที่ดี

ในระดับแอปพลิเคชัน ...

มากสำหรับมุมมองต่อชั้นของสิ่งต่าง ๆ สมมติว่าคุณมีกลุ่มของคลาสที่ปฏิบัติตามกฎ "อย่ายกตัวอย่างผู้ร่วมมือของคุณ" และต้องการสร้างแอปพลิเคชันจากพวกเขา สิ่งที่ง่ายที่สุดที่จะทำคือการใช้รหัสเก่าที่ดี(เครื่องมือที่มีประโยชน์จริง ๆ สำหรับการเรียกตัวสร้างคุณสมบัติและวิธีการ!) เพื่อเขียนกราฟวัตถุที่คุณต้องการและโยนอินพุตลงไป (ใช่แล้วบางส่วนของวัตถุเหล่านั้นในกราฟของคุณจะเป็นวัตถุ - โรงงานซึ่งได้รับการส่งต่อเป็นผู้ทำงานร่วมกันไปยังวัตถุที่มีอายุยืนยาวอื่น ๆ ในกราฟพร้อมให้บริการ ... คุณไม่สามารถสร้างวัตถุล่วงหน้าทั้งหมดได้! )

... ความต้องการของคุณ 'ยืดหยุ่น' กำหนดค่ากราฟวัตถุของแอป ...

ขึ้นอยู่กับวัตถุประสงค์อื่น ๆ (ไม่เกี่ยวข้องกับรหัส) ของคุณคุณอาจต้องการให้ผู้ใช้ปลายทางควบคุมกราฟวัตถุที่ปรับใช้ สิ่งนี้ทำให้คุณไปในทิศทางของโครงร่างการกำหนดค่าของการเรียงลำดับอย่างใดอย่างหนึ่งไม่ว่าจะเป็นไฟล์ข้อความที่คุณออกแบบเองพร้อมชื่อ / ค่าบางคู่ไฟล์ XML, DSL ที่กำหนดเอง, ภาษาคำอธิบายกราฟที่ประกาศเช่น YAML ภาษาสคริปต์ที่จำเป็นเช่น JavaScript หรืออย่างอื่นที่เหมาะสมกับงานในมือ ไม่ว่าจะเป็นการสร้างกราฟวัตถุที่ถูกต้องในแบบที่ตรงกับความต้องการของผู้ใช้ของคุณ

... อาจเป็นกำลังสำคัญในการออกแบบ

ในที่สุดสถานการณ์ชนิดที่คุณสามารถเลือกที่จะใช้วิธีการทั่วไปมากและให้ผู้ใช้ปลายทั่วไปกลไกสำหรับ 'เดินสายขึ้น' วัตถุกราฟของพวกเขาเลือกและยังช่วยให้พวกเขาที่จะให้ความเข้าใจที่เป็นรูปธรรมของการเชื่อมต่อไปยัง รันไทม์! (เอกสารของคุณเป็นอัญมณีที่เปล่งประกายผู้ใช้ของคุณฉลาดและคุ้นเคยอย่างน้อยก็มีเค้าโครงคร่าวๆของกราฟวัตถุของแอปพลิเคชันของคุณ แต่ไม่ได้มีประโยชน์สำหรับคอมไพเลอร์) สถานการณ์นี้อาจเกิดขึ้นในทางทฤษฎีในบางสถานการณ์ 'องค์กร'

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

คุณเป็นคนยากจน!

เนื่องจากคุณไม่ได้คิดที่จะเขียนทุกอย่างด้วยตัวเอง (แต่อย่างจริงจังลองดู YAML สำหรับภาษาของคุณ) คุณกำลังใช้กรอบ DI บางอย่าง

คุณอาจไม่มีตัวเลือกในการใช้ constructor-injection ถึงแม้ว่ามันจะสมเหตุสมผล (ผู้ทำงานร่วมกันจะไม่เปลี่ยนแปลงตลอดอายุการใช้งานของวัตถุ) ดังนั้นจึงบังคับให้คุณใช้ Setter Injection (แม้เมื่อ ผู้ทำงานร่วมกันจะไม่เปลี่ยนแปลงตลอดอายุการใช้งานของวัตถุและแม้ว่าจะไม่มีเหตุผลเชิงตรรกะว่าทำไมการใช้งานอินเทอร์เฟซที่เป็นรูปธรรมทั้งหมดต้องมีผู้ทำงานร่วมกันประเภทเฉพาะ) ถ้าเป็นเช่นนั้นตอนนี้คุณกำลังตกอยู่ในอันตรายอย่างหนักแม้จะมี 'อินเทอร์เฟซที่ใช้' อย่างขยันขันแข็งตลอดทั้งฐานรหัสของคุณ - สยองขวัญ!

หวังว่าคุณจะใช้ DI Framework ที่ให้ตัวเลือกในการฉีด Constructor และผู้ใช้ของคุณรู้สึกไม่พอใจที่คุณไม่ต้องใช้เวลาคิดเกี่ยวกับสิ่งที่พวกเขาต้องการเพื่อกำหนดค่าและให้ UI เหมาะสมกับงานมากขึ้น ที่มือ. (แม้ว่าจะยุติธรรมคุณอาจลองคิดดู แต่ JavaEE ทำให้คุณผิดหวังและคุณต้องหันไปใช้วิธีแฮ็คที่น่ากลัวนี้)

Bootnote

คุณไม่เคยใช้ Google Guice มาก่อนซึ่งจะช่วยให้โคเดอร์คุณมีวิธีจัดการกับการเขียนกราฟวัตถุด้วยรหัส ... โดยการเขียนโค้ด โอ๊ะ!


0

ผู้คนมากมายพูดที่นี่ว่า DI เป็นกลไกในการหาค่าเริ่มต้นของตัวแปรอินสแตนซ์

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

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

ตัวอย่างเช่น: การเชื่อมต่อฐานข้อมูล, บริบทความปลอดภัย, สิ่งอำนวยความสะดวกในการบันทึก, ไฟล์กำหนดค่าเป็นต้น

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

สำหรับทรัพยากรประเภทนี้ค่อนข้างชัดเจนว่ามันไม่สมเหตุสมผลเลยที่จะย้ายพวกมันไปรอบ ๆ ผ่านรายการพารามิเตอร์ดังนั้น DI จึงถูกประดิษฐ์ขึ้น

บันทึกล่าสุดคุณต้องมีคอนเทนเนอร์ DI ซึ่งจะทำการฉีดจริง ... (อีกครั้งเพื่อปล่อยทั้งผู้โทรและ callee จากรายละเอียดการใช้งาน)


-2

หากคุณผ่านประเภทการอ้างอิงที่เป็นนามธรรมแล้วรหัสการโทรสามารถผ่านในวัตถุใด ๆ ที่ขยาย / ใช้ประเภทนามธรรม ดังนั้นในอนาคตคลาสที่ใช้วัตถุสามารถใช้วัตถุใหม่ที่สร้างขึ้นโดยไม่ต้องแก้ไขโค้ด


-4

นี่เป็นอีกตัวเลือกหนึ่งนอกเหนือจากคำตอบของอมร

ใช้ผู้สร้าง:

ชั้นฟู
    บาร์บาร์ส่วนตัว
    Qux ส่วนตัว qux;

    Foo ส่วนตัว () {
    }

    Foo สาธารณะ (ผู้สร้างเครื่องมือสร้าง) {
        this.bar = builder.bar;
        this.qux = builder.qux;
    }

    สร้างคลาสคงที่สาธารณะ
        บาร์บาร์ส่วนตัว
        Qux ส่วนตัว qux;
        Buider สาธารณะ setBar (บาร์บาร์) {
            this.bar = bar;
            ส่งคืนสิ่งนี้
        }
        public Builder setQux (Qux qux) {
            this.qux = qux;
            ส่งคืนสิ่งนี้
        }
        Foo บิวด์สาธารณะ () {
            คืน Foo ใหม่ (อันนี้);
        }
    }
}

// ในการผลิต:
Foo foo = ใหม่ Foo.Builder ()
    .setBar (แถบใหม่ ())
    .setQux (ใหม่ Qux ())
    .สร้าง();

// ในการทดสอบ:
Foo testFoo = ใหม่ Foo.Builder ()
    .setBar (ใหม่ BarMock ())
    .setQux (ใหม่ Qux ())
    .สร้าง();

นี่คือสิ่งที่ฉันใช้สำหรับการอ้างอิง ฉันไม่ใช้กรอบ DI ใด ๆ พวกเขาได้รับในทาง


4
สิ่งนี้ไม่ได้เพิ่มคำตอบอื่น ๆ มากไปกว่าปีที่แล้วและไม่ได้อธิบายว่าทำไมผู้สร้างอาจช่วยให้ผู้ถามเข้าใจการพึ่งพา

@Snowman มันเป็นอีกทางเลือกแทน DI ฉันขอโทษที่คุณไม่เห็นเช่นนั้น ขอบคุณสำหรับการลงคะแนน
user3155341

1
ผู้ถามต้องการที่จะเข้าใจว่า DI ง่ายกว่าการส่งพารามิเตอร์หรือไม่เขาไม่ได้ขอทางเลือกอื่นสำหรับ DI

1
ตัวอย่างของ OP นั้นดีมาก ตัวอย่างของคุณมีความซับซ้อนมากขึ้นสำหรับบางสิ่งที่เป็นแนวคิดง่ายๆ พวกเขาไม่ได้แสดงปัญหาของคำถาม
Adam Zuckerman

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