ฉันได้รับการฉีดพึ่งพา แต่บางคนสามารถช่วยฉันเข้าใจความต้องการคอนเทนเนอร์ IoC ได้หรือไม่


15

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

โดยทั่วไปแล้วฉันแบ่งตรรกะทางธุรกิจของฉันออกเป็นคลาสที่อาจมีลักษณะเช่นนี้:

public class SomeBusinessOperation
{
    private readonly IDataRepository _repository;

    public SomeBusinessOperation(IDataRespository repository = null)
    {
        _repository = repository ?? new ConcreteRepository();
    }

    public SomeType Run(SomeRequestType request)
    {
        // do work...
        var results = _repository.GetThings(request);

        return results;
    }
}

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

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


1
บ่อยครั้งที่การพึ่งพาเวอร์ชันทางเลือกเริ่มต้นของการอ้างอิงไม่สมเหตุสมผลจริงๆ
Ben Aaronson

คุณหมายความว่าอย่างไร? "fallback" เป็นคลาสคอนกรีตที่ใช้งานได้ค่อนข้างตลอดเวลายกเว้นในการทดสอบหน่วย ในผลนี้จะเป็นคลาสเดียวกันที่ลงทะเบียนในภาชนะ
Sinaesthetic

ใช่ แต่ด้วยคอนเทนเนอร์: (1) วัตถุอื่น ๆ ทั้งหมดในคอนเทนเนอร์ได้รับอินสแตนซ์เดียวกันConcreteRepositoryและ (2) คุณสามารถจัดหาการพึ่งพาเพิ่มเติมให้กับConcreteRepository(การเชื่อมต่อฐานข้อมูลจะเป็นเรื่องธรรมดาเช่น)
Jules

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

ฉันจะได้รับเสื้อยืดที่ทำให้ "พารามิเตอร์ฟังก์ชั่น - การฉีดขึ้นอยู่กับการประดิษฐ์!
เกรแฮม

คำตอบ:


2

คอนเทนเนอร์ IoC ไม่ได้เกี่ยวกับกรณีที่คุณมีการอ้างอิงเพียงครั้งเดียว มันเกี่ยวกับกรณีที่คุณมี 3 การอ้างอิงและพวกเขามีการอ้างอิงหลายอย่างที่มีการอ้างอิง ฯลฯ

นอกจากนี้ยังช่วยให้คุณรวมศูนย์การแก้ปัญหาของการพึ่งพาและการจัดการวงจรชีวิตของการพึ่งพา


10

มีสาเหตุหลายประการที่คุณอาจต้องการใช้คอนเทนเนอร์ IoC

ที่กำลังอ้างอิง

คุณสามารถใช้คอนเทนเนอร์ IoC เพื่อแก้ไขคลาสคอนกรีตจาก dll ที่ไม่มีการอ้างอิง ซึ่งหมายความว่าคุณสามารถพึ่งพาสิ่งที่เป็นนามธรรมทั้งหมด - เช่นอินเทอร์เฟซ

หลีกเลี่ยงการใช้ new

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

เขียนกับบทคัดย่อ

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

หลีกเลี่ยงรหัสเปราะ

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

การจัดการอายุการใช้งานและการล้างข้อมูลที่ไม่มีการจัดการต่อ

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

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


22
สิ่งเหล่านี้ไม่ใช่ข้อดีของภาชนะบรรจุ IoC แต่เป็นข้อดีของการฉีดแบบพึ่งพาซึ่งสามารถทำได้อย่างง่ายดายด้วย DI ของชายยากจน
Ben Aaronson

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

ดีด้วยเหตุผลสองประการสุดท้ายที่คุณเพิ่มตั้งแต่ความคิดเห็นของฉันมีภาชนะที่เฉพาะเจาะจงและมีทั้งข้อโต้แย้งที่แข็งแกร่งมากในความคิดของฉัน
เบน Aaronson

"หลีกเลี่ยงการใช้ของใหม่" - ยังเป็นอุปสรรคต่อการวิเคราะห์รหัสคงที่เพื่อให้คุณได้เริ่มต้นใช้งานบางอย่างเช่นนี้hmemcpy.github.io/AgentMulder ผลประโยชน์อื่น ๆ ที่คุณอธิบายไว้ในวรรคนี้เกี่ยวข้องกับ DI ไม่ใช่ IoC นอกจากนี้คลาสของคุณจะยังคงอยู่คู่กันถ้าคุณหลีกเลี่ยงการใช้ใหม่แต่จะใช้ประเภทคอนกรีตแทนอินเทอร์เฟซสำหรับพารามิเตอร์
Den

1
โดยรวมแล้ววาดภาพ IoC เป็นสิ่งที่ไม่มีข้อบกพร่องตัวอย่างเช่นไม่มีการกล่าวถึงข้อเสียที่ยิ่งใหญ่: การผลักคลาสของข้อผิดพลาดเข้าสู่รันไทม์แทนที่จะรวบรวมเวลา
Den

2

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

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

การรวมกันของหลักการเหล่านี้ตามตัวอย่าง (ขออภัยสำหรับรหัสคุณภาพต่ำฉันใช้ภาษา ad-hoc แทนภาษา C # เนื่องจากฉันไม่ทราบ):

  1. ไม่มี SRP ไม่มี IoC

    class SomeHighLevelService
    {
        public doFooBar(){
            Crap crap = doFoo();
            doBar(crap);
        }
    
        public Crap doFoo(){
            //...
            return crap;
        }
    
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  2. ใกล้เคียงกับ SRP ไม่มี IoC

    class SomeHighLevelService
    {
        public SomeHighLevelService(){
            Foo foo = new Foo();
            Bar bar = new Bar();
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  3. ใช่ SRP ไม่ใช่ IoC

    class HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new SomeHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new Foo();
        }
    
        private Bar getBar(){
            return new Bar();
        }
    }
    
    class SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    HighLevelServiceProvider provider = new HighLevelServiceProvider();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();
  4. ใช่ SRP ใช่ IoC

    interface HighLevelServiceProvider {
        SomeHighLevelService getSomeHighLevelService();
    }
    
    interface SomeHighLevelService {
        doFooBar();
    }
    
    interface Foo {
        Crap doFoo();
    }
    
    interface Bar {
        doBar(Crap crap);
    }
    
    
    class ConcreteHighLevelServiceContainer implements HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new ConcreteHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new ConcreteFoo();
        }
    
        private Bar getBar(){
            return new ConcreteBar();
        }
    }
    
    class ConcreteHighLevelService implements SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class ConcreteFoo implements Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class ConcreteBar implements Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    
    HighLevelServiceProvider provider = new ConcreteHighLevelServiceContainer();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();

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

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