Mengejek memperkenalkan penanganan dalam kode produksi

15

Dengan asumsi antarmuka IReader, implementasi antarmukaIeader ReaderImplementation, dan kelas ReaderConsumer yang mengkonsumsi dan memproses data dari pembaca.

public interface IReader
{
     object Read()
}

Penerapan

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

Konsumen:

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    
    }
}

Untuk menguji ReaderConsumer dan pemrosesan saya menggunakan tiruan dari IReader. Jadi ReaderConsumer menjadi:

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
        }
    }
}

Dalam solusi ini mengejek memperkenalkan kalimat if untuk kode produksi karena hanya konstruktor mengejek yang menyediakan instance dari antarmuka.

Saat menulis ini, saya menyadari bahwa blok coba-akhirnya agak tidak terkait karena ada untuk menangani pengguna mengubah lokasi selama waktu aplikasi dijalankan.

Secara keseluruhan rasanya bau, bagaimana mungkin ditangani lebih baik?

kristian mo
sumber
16
Biasanya, ini bukan masalah karena konstruktor dengan ketergantungan disuntikkan, akan menjadi satu-satunya konstruktor. Apakah keluar dari pertanyaan untuk ReaderConsumermandiri ReaderImplementation?
Chris Wohlert
Saat ini akan sulit untuk menghapus ketergantungan. Dengan melihatnya sedikit lebih banyak, saya memiliki masalah yang lebih dalam daripada hanya ketergantungan pada ReaderImplemenatation. Karena ReaderConsumer dibuat saat startup dari sebuah pabrik, bertahan selama masa aplikasi, dan menerima perubahan dari pengguna, itu memerlukan beberapa pijatan ekstra. Mungkin konfigurasi / input pengguna dapat ada sebagai objek dan kemudian ReaderConsumer dan ReaderImplementation dapat dibuat dengan cepat. Kedua jawaban yang diberikan memecahkan kasus yang lebih umum cukup baik.
kristian mo
3
Ya . Ini adalah titik TDD: harus menulis tes terlebih dahulu menyiratkan desain yang lebih dipisahkan (jika tidak Anda tidak dapat menulis tes unit ...). Ini membantu untuk membuat kode lebih dapat dipertahankan dan juga dapat dikembangkan.
Bakuriu
Cara yang baik untuk mendeteksi bau yang dapat diselesaikan dengan Dependency Injection, adalah mencari kata kunci 'baru'. Jangan mulai ketergantungan Anda. Suntikkan saja.
Eternal21

Jawaban:

67

Alih-alih menginisialisasi pembaca dari metode Anda, pindahkan baris ini

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

Ke dalam konstruktor tanpa parameter default.

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

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

Tidak ada yang namanya "mock constructor", jika kelas Anda memiliki ketergantungan yang diperlukan agar dapat berfungsi, maka konstruktor harus menyediakan hal itu, atau membuatnya.

Bebek karet
sumber
3
skor ++ ... kecuali dari sudut pandang DI, konstruktor default itu pasti bau kode.
Mathieu Guindon
3
@ Mat'sMug tidak ada yang salah dengan implementasi standar. Kurangnya rantai ctor adalah bau di sini. =;) -
RubberDuck
Oh, ya ada - jika Anda tidak memiliki buku itu, artikel ini tampaknya menggambarkan anti-pola injeksi bajingan dengan cukup baik (hanya membaca skim).
Mathieu Guindon
3
@ Mat'sMug: Anda menafsirkan DI secara dogmatis. Jika Anda tidak perlu menyuntikkan konstruktor default, maka Anda tidak perlu.
Robert Harvey
3
Buku @ Mat'sMug tertaut juga berbicara tentang "default yang dapat diterima" untuk dependensi baik-baik saja (meskipun mengacu pada injeksi properti). Mari kita begini: pendekatan dua-konstruktor lebih mahal dalam hal kesederhanaan dan pemeliharaan daripada pendekatan satu-konstruktor, dan dengan pertanyaan yang terlalu disederhanakan untuk kejelasan, biayanya tidak dibenarkan, tetapi mungkin dalam beberapa kasus .
Carl Leth
54

Anda hanya memerlukan konstruktor tunggal:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

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

dalam kode produksi Anda:

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

dalam tes Anda:

var rc = new ReaderConsumer(new MockImplementation(0));
Ewan
sumber
13

Lihat ke Injeksi Ketergantungan dan Inversi Kontrol

Baik Ewan dan RubberDuck memiliki jawaban yang sangat baik. Tapi saya ingin menyebutkan area lain untuk melihat ke mana Dependency Injection (DI) dan Inversion of Control (IoC). Kedua pendekatan ini memindahkan masalah yang Anda alami ke dalam kerangka / pustaka sehingga Anda tidak perlu khawatir tentang hal itu.

Contoh Anda sederhana, dan cepat dibuang, tetapi, mau tidak mau Anda akan membangun di atasnya dan Anda akan berakhir dengan ton konstruktor atau rutin inisialisasi yang terlihat seperti:

var foo = Foo baru (Bilah baru (Baz baru (), Quz baru ()), Foo2 baru ());

Dengan DI / IoC Anda menggunakan pustaka yang memungkinkan Anda untuk menjabarkan aturan untuk mencocokkan antarmuka dengan implementasi dan kemudian Anda hanya mengatakan "Beri aku Foo" dan itu berhasil bagaimana memasang semuanya.

Ada banyak Kontainer IoC yang sangat ramah (begitu sebutannya) di luar sana, dan saya akan merekomendasikan satu untuk dilihat, tetapi, silakan jelajahi, karena ada banyak sekali pilihan bagus.

Yang sederhana untuk memulai adalah:

http://www.ninject.org/

Berikut daftar untuk dijelajahi:

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

Reginald Blue
sumber
7
Kedua jawaban Ewan dan RubberDuck menunjukkan DI. DI / IoC bukan tentang alat atau kerangka kerja, ini tentang membuat dan menyusun kode sedemikian rupa sehingga kontrol dibalik dan dependensi disuntikkan. Buku Mark Seemann melakukan pekerjaan yang hebat (luar biasa!) Dalam menjelaskan bagaimana DI / IoC sepenuhnya dapat dicapai tanpa kerangka kerja IoC, dan mengapa dan kapan kerangka kerja IoC menjadi ide yang baik (petunjuk: ketika Anda memiliki dependensi dependensi dependensi dari .. .) =)
Mathieu Guindon
Juga ... +1 karena Ninject mengagumkan, dan setelah membaca ulang jawaban Anda tampaknya baik-baik saja. Paragraf pertama berbunyi agak seperti jawaban Ewan dan RubberDuck bukan tentang DI, itulah yang mendorong komentar pertama saya.
Mathieu Guindon
1
@ Mat'sMug Pasti mengerti maksud Anda. Saya mencoba untuk mendorong OP untuk melihat DI / IoC yang poster lainnya, pada dasarnya, menggunakan (Ewan adalah Poor Man's DI), tetapi tidak menyebutkan. Keuntungannya, tentu saja, menggunakan kerangka kerja adalah bahwa hal itu akan membenamkan pengguna di dunia DI dengan banyak contoh konkret yang mudah-mudahan akan memandu mereka untuk memahami apa yang mereka lakukan ... meskipun ... tidak selalu. :-) Dan, tentu saja, saya menyukai buku Mark Seemann. Itu salah satu favorit saya.
Reginald Blue
Terima kasih atas tip tentang kerangka DI, tampaknya menjadi cara untuk memperbaiki kekacauan ini dengan benar :)
kristian mo