การเยาะเย้ยแนะนำการจัดการในรหัสการผลิต


15

สมมติว่ามีอินเทอร์เฟซ IReader การนำไปใช้ของตัวอ่าน IReader อินเตอร์เฟส ReaderImplementation และคลาส ReaderConsumer ที่ใช้และประมวลผลข้อมูลจากเครื่องอ่าน

public interface IReader
{
     object Read()
}

การดำเนินงาน

public class ReaderImplementation
{
    ...
    public object Read()
    {
        ...
    }
}

ผู้บริโภค:

public class ReaderConsumer()
{
    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // read some data
    public object ReadData()
    {
        IReader reader = new ReaderImplementation(this.location)
        data = reader.Read()
        ...
        return processedData    
    }
}

สำหรับการทดสอบ ReaderConsumer และการประมวลผลฉันใช้จำลองของ IReader ดังนั้น ReaderConsumer จึงกลายเป็น:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // mock constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader
    }

    // read some data
    public object ReadData()
    {
        try
        {
            if(this.reader == null)
            {
                 this.reader = new ReaderImplementation(this.location)
            }

            data = reader.Read()
            ...
            return processedData    
        }
        finally
        {
            this.reader = null
        }
    }
}

ในโซลูชันนี้การเยาะเย้ยแนะนำประโยค if สำหรับรหัสการผลิตเนื่องจากตัวสร้างการจำลองเท่านั้นที่ให้อินสแตนซ์ของอินเทอร์เฟซ

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

โดยรวมแล้วรู้สึกเหม็นวิธีการจัดการที่ดีขึ้น


16
โดยทั่วไปแล้วนี่ไม่ใช่ปัญหาเนื่องจากตัวสร้างที่มีการพึ่งพาการฉีดจะเป็นตัวสร้างเท่านั้น มันReaderConsumerเป็นไปReaderImplementationไม่ได้หรือไม่ที่จะทำให้เป็นอิสระ?
Chris Wohlert

ปัจจุบันมันยากที่จะลบการอ้างอิง โดยดูที่อีกเล็กน้อยฉันมีปัญหาลึกกว่าพึ่งพึ่งพา ReaderImplemenatation เนื่องจาก ReaderConsumer ถูกสร้างขึ้นในระหว่างการเริ่มต้นจากโรงงานให้คงอยู่ตลอดอายุการใช้งานของแอปพลิเคชันและยอมรับการเปลี่ยนแปลงจากผู้ใช้จึงต้องมีการนวดเป็นพิเศษ อาจป้อนข้อมูลการกำหนดค่า / ผู้ใช้อาจมีอยู่เป็นวัตถุและจากนั้น ReaderConsumer และ ReaderImplementation สามารถสร้างได้ทันที ทั้งสองคำตอบที่ได้รับแก้ปัญหากรณีทั่วไปมากขึ้นค่อนข้างดี
kristian mo

3
ใช่แล้ว นี่คือจุดสำคัญของ TDD: ต้องเขียนการทดสอบก่อนแสดงถึงการออกแบบที่แยกออกจากกันมากขึ้น (มิฉะนั้นคุณจะไม่สามารถเขียนการทดสอบหน่วย ... ) สิ่งนี้จะช่วยให้โค้ดสามารถบำรุงรักษาได้มากขึ้นและสามารถขยายได้
Bakuriu

วิธีที่ดีในการตรวจจับกลิ่นที่สามารถแก้ไขได้ด้วยการพึ่งพาการฉีดกำลังมองหาคำหลัก 'ใหม่' ไม่ใหม่ขึ้นอยู่กับการพึ่งพาของคุณ ฉีดพวกมันแทน
นิรันดร์ 21

คำตอบ:


67

แทนที่จะเริ่มต้นเครื่องอ่านจากวิธีการของคุณให้ย้ายบรรทัดนี้

{
    this.reader = new ReaderImplementation(this.location)
}

เข้าสู่ Constructor ที่ไม่มีพารามิเตอร์

public ReaderConsumer()
{
    this.reader = new ReaderImplementation(this.location)
}

public ReaderConsumer(IReader reader)
{
    this.reader = reader
}

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


3
คะแนน ++ ... ยกเว้นจากจุดยืน DI ตัวสร้างเริ่มต้นนั้นเป็นรหัสที่แน่นอน
Mathieu Guindon

3
@ Mat'sMug ไม่มีอะไรผิดปกติกับการใช้งานเริ่มต้น การขาด ctor ที่ผูกมัดคือกลิ่นที่นี่ =;) -
RubberDuck

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

3
@ Mat'sMug: คุณตีความ DI ดื้อรั้น หากคุณไม่จำเป็นต้องฉีดคอนสตรัคเตอร์เริ่มต้นคุณก็ไม่จำเป็นต้องทำ
Robert Harvey

3
หนังสือ @ Mat'sMug ที่เชื่อมโยงนั้นยังพูดถึง "ค่าเริ่มต้นที่ยอมรับได้" สำหรับการพึ่งพาที่ดี (แม้ว่าจะหมายถึงการฉีดคุณสมบัติ) ลองใช้วิธีนี้: วิธีการแบบสองคอนสตรัคเตอร์มีค่าใช้จ่ายมากขึ้นในแง่ของความเรียบง่ายและความสามารถในการบำรุงรักษามากกว่าวิธีการแบบหนึ่งคอนสตรัคเตอร์และด้วยคำถามที่ง่ายกว่าสำหรับความชัดเจน .
Carl Leth

54

คุณต้องการเพียงตัวสร้างเดียว:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader;
    }

ในรหัสการผลิตของคุณ:

var rc = new ReaderConsumer(new ReaderImplementation(0));

ในการทดสอบของคุณ:

var rc = new ReaderConsumer(new MockImplementation(0));

13

ดูการพึ่งพาการฉีดและการผกผันของการควบคุม

ทั้ง Ewan และ RubberDuck มีคำตอบที่ยอดเยี่ยม แต่ฉันอยากจะพูดถึงพื้นที่อื่นเพื่อดูว่ามันคือ Dependency Injection (DI) และ Inversion of Control (IoC) วิธีการทั้งสองนี้ทำให้เกิดปัญหาที่คุณพบในเฟรมเวิร์ก / ไลบรารีเพื่อที่คุณจะได้ไม่ต้องกังวลกับมัน

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

var foo = new Foo (แถบใหม่ (ใหม่ Baz (), ใหม่ Quz ()), ใหม่ Foo2 ());

ด้วย DI / IoC คุณใช้ไลบรารีที่ให้คุณสะกดกฎสำหรับอินเทอร์เฟซการจับคู่กับการใช้งานและจากนั้นคุณเพียงแค่พูดว่า "Give me a Foo" และมันจะอธิบายวิธีการเชื่อมโยงทั้งหมด

มีจำนวนมากที่เป็นมิตร IoC ตู้คอนเทนเนอร์ (ตามที่พวกเขาถูกเรียกว่า) ออกมีและฉันจะแนะนำให้คนดู แต่โปรดสำรวจเพราะมีตัวเลือกที่ดีมาก

วิธีง่ายๆในการเริ่มต้นคือ:

http://www.ninject.org/

นี่คือรายการของการสำรวจ:

http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx


7
คำตอบของทั้ง Ewan และ RubberDuck แสดงให้เห็น DI DI / IoC ไม่ได้เกี่ยวกับเครื่องมือหรือเฟรมเวิร์ก แต่เป็นเรื่องเกี่ยวกับการสร้างและจัดโครงสร้างโค้ดในลักษณะที่การควบคุมกลับด้านและมีการแทรกการอ้างอิง หนังสือของ Mark Seemannทำงานได้ยอดเยี่ยม (ยอดเยี่ยม!) ในการอธิบายว่า DI / IoC สามารถบรรลุได้อย่างสมบูรณ์โดยปราศจากกรอบ IoC และทำไมและเมื่อกรอบ IoC กลายเป็นแนวคิดที่ดี (คำใบ้: เมื่อคุณมีการพึ่งพาการพึ่งพาของ .. .) =)
Mathieu Guindon

นอกจากนี้ ... +1 เพราะ Ninject นั้นยอดเยี่ยมและเมื่ออ่านคำตอบของคุณอีกครั้งก็ดูเหมือนดี ย่อหน้าแรกอ่านเล็กน้อยเช่นคำตอบของ Ewan และ RubberDuck ไม่ได้เกี่ยวกับ DI ซึ่งเป็นสิ่งที่กระตุ้นความคิดเห็นแรกของฉัน
Mathieu Guindon

1
@ Mat'sMug รับคะแนนของคุณอย่างแน่นอน ฉันพยายามที่จะสนับสนุนให้ OP ดูเป็น DI / IoC ซึ่งโปสเตอร์อื่น ๆ มีผลใช้ (Ewan's เป็น Poor Man's DI) แต่ไม่ได้พูดถึง แน่นอนว่าข้อดีของการใช้เฟรมเวิร์กคือมันจะทำให้ผู้ใช้ในโลก DI มีตัวอย่างที่เป็นรูปธรรมมากมายซึ่งหวังว่าจะเป็นแนวทางให้พวกเขาเข้าใจสิ่งที่พวกเขากำลังทำ ... แม้ว่าจะไม่เสมอไป :-) และแน่นอนฉันชอบหนังสือของมาร์กเซมันน์ มันเป็นหนึ่งในรายการโปรดของฉัน
Reginald Blue

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