Pertanyaan Alternatif yang layak untuk injeksi ketergantungan?


Saya tidak suka injeksi ketergantungan berbasis konstruktor.

Saya percaya bahwa itu meningkatkan kompleksitas kode dan mengurangi pemeliharaan, dan saya ingin tahu apakah ada alternatif yang layak.

Saya tidak berbicara tentang konsep memisahkan implementasi dari antarmuka, dan memiliki cara menyelesaikan secara dinamis (secara rekursif) satu set objek dari sebuah antarmuka. Saya sepenuhnya mendukung itu. Namun, cara tradisional konstruktor berdasarkan melakukan hal ini tampaknya memiliki beberapa masalah.

1) Semua tes bergantung pada konstruktor.

Setelah menggunakan DI secara ekstensif dalam proyek MVC 3 C # selama setahun terakhir, saya menemukan kode kami penuh dengan hal-hal seperti ini:

public interface IMyClass {
   ...
}

public class MyClass : IMyClass { 

  public MyClass(ILogService log, IDataService data, IBlahService blah) {
    _log = log;
    _blah = blah;
    _data = data;
  }

  ...
}

Masalah: Jika saya memerlukan layanan lain dalam implementasi saya, saya harus memodifikasi konstruktor; dan itu berarti bahwa semua tes unit untuk istirahat kelas ini.

Bahkan tes yang tidak ada hubungannya dengan fungsi baru memerlukan setidaknya refactoring untuk menambahkan parameter tambahan dan menyuntikkan tiruan untuk argumen itu.

Ini sepertinya masalah kecil, dan alat-alat otomatis seperti resharper membantu, tetapi tentu saja menjengkelkan ketika perubahan sederhana seperti ini menyebabkan 100+ tes untuk dilanggar. Secara praktis saya telah melihat orang melakukan hal-hal bodoh untuk menghindari mengubah konstruktor daripada menggigit peluru dan memperbaiki semua tes ketika ini terjadi.

2) Instance layanan dapat diedarkan secara tidak perlu, meningkatkan kerumitan kode.

public class MyWorker {
  public MyWorker(int x, IMyClass service, ILogService logs) {
    ...    
  }
}

Membuat instance dari kelas ini jika hanya mungkin jika saya berada di dalam konteks di mana layanan yang diberikan tersedia dan secara otomatis telah diselesaikan (misalnya. Controller) atau, sayangnya, dengan memberikan contoh layanan ke beberapa rantai kelas pembantu.

Saya melihat kode seperti ini sepanjang waktu:

public class BlahHelper {

  // Keep so we can create objects later
  var _service = null;

  public BlahHelper(IMyClass service) {
    _service = service;
  }

  public void DoSomething() {
    var worker = new SpecialPurposeWorker("flag", 100, service);
    var status = worker.DoSomethingElse();
    ...

  }
}

Dalam hal contoh tidak jelas, apa yang saya bicarakan adalah melewatkan DI contoh antarmuka diselesaikan melalui beberapa lapisan dari tak ada alasan selain di lapisan bawah mereka perlu menyuntikkan sesuatu.

Jika kelas tidak bergantung pada layanan, itu harus memiliki ketergantungan pada layanan itu. Ide ini bahwa ada ketergantungan 'sementara' di mana kelas tidak menggunakan sebuah layanan tetapi hanya meneruskannya, menurut saya, tidak masuk akal.

Namun, saya tidak tahu solusi yang lebih baik.

Adakah sesuatu yang memberikan manfaat DI tanpa masalah ini?

Saya sudah merenungkan menggunakan kerangka DI di dalam konstruktor, bukan karena ini menyelesaikan beberapa masalah:

public MyClass() {
  _log = Register.Get().Resolve<ILogService>();
  _blah = Register.Get().Resolve<IBlahService>();
  _data = Register.Get().Resolve<IDataService>();
}

Apakah ada kerugian untuk melakukan ini?

Ini berarti bahwa tes unit harus memiliki 'pengetahuan sebelumnya' dari kelas untuk mengikat mengolok-olok untuk jenis yang benar selama tes init, tetapi saya tidak dapat melihat kerugian lainnya.

NB. Contoh saya ada di c #, tetapi saya juga menemukan masalah yang sama dalam bahasa lain, dan khususnya bahasa dengan dukungan perkakas yang kurang matang adalah masalah besar.


32
2018-02-06 04:06


asal


Jawaban:


Menurut saya, akar masalah dari semua masalah adalah tidak melakukan DI dengan benar. Tujuan utama menggunakan konstruktor DI adalah untuk menyatakan dengan jelas semua dependensi dari beberapa kelas. Jika sesuatu bergantung pada sesuatu, Anda selalu memiliki dua pilihan: membuat ketergantungan ini eksplisit atau menyembunyikannya dalam beberapa mekanisme (cara ini cenderung membawa lebih banyak sakit kepala daripada keuntungan).

Biarkan melalui laporan Anda:

Semua tes bergantung pada konstruktor.

[menggunting]

Masalah: Jika saya memerlukan layanan lain dalam implementasi saya, saya harus memodifikasi konstruktor; dan itu berarti bahwa semua tes unit untuk istirahat kelas ini.

Membuat kelas bergantung pada beberapa layanan lain adalah perubahan yang agak besar. Jika Anda memiliki beberapa layanan yang menerapkan fungsi yang sama, saya berpendapat bahwa ada masalah desain. Mengejek benar dan membuat tes memuaskan SRP (yang dalam hal tes unit bermuara pada "Tulis tes terpisah per masing-masing kasus uji") dan independen harus menyelesaikan masalah ini.

2) Instance layanan dapat diedarkan secara tidak perlu, meningkatkan kerumitan kode.

Salah satu kegunaan DI yang paling umum adalah memisahkan pembuatan objek dari logika bisnis. Dalam kasus Anda, kami melihat bahwa apa yang benar-benar Anda butuhkan adalah membuat beberapa Pekerja yang pada gilirannya membutuhkan beberapa dependensi yang disuntikkan melalui seluruh grafik objek. Pendekatan terbaik untuk menyelesaikan masalah ini adalah jangan pernah melakukannya newdalam logika bisnis. Untuk kasus-kasus seperti itu saya lebih suka menyuntikkan pabrik pekerja yang mengabstraksi kode bisnis dari penciptaan pekerja yang sebenarnya.

Saya sudah merenungkan menggunakan kerangka DI di dalam konstruktor, bukan karena ini menyelesaikan beberapa masalah:

public MyClass() {
  _log = Register.Get().Resolve<ILogService>();
  _blah = Register.Get().Resolve<IBlahService>();
  _data = Register.Get().Resolve<IDataService>();
}

Apakah ada kerugian untuk melakukan ini?

Sebagai manfaat, Anda akan mendapatkan semua kerugian menggunakan Singleton pola (kode yang tidak dapat diuji dan ruang status yang sangat besar dari aplikasi Anda).

Jadi saya akan mengatakan bahwa DI harus dilakukan dengan benar (seperti alat lain). Solusi untuk masalah Anda (IMO) terletak pada pemahaman DI dan pendidikan anggota tim Anda.


18
2018-02-06 04:27



Ini tergoda untuk kesalahan Konstruktor Injeksi untuk masalah ini, tetapi mereka sebenarnya gejala implementasi yang tidak tepat lebih dari kerugian dari Konstruktor Injeksi.

Mari kita lihat setiap masalah nyata secara individual.

Semua tes bergantung pada konstruktor.

Masalahnya di sini adalah benar-benar bahwa tes unit erat digabungkan ke konstruktor. Ini sering dapat diperbaiki dengan sederhana Pabrik SUT - sebuah konsep yang dapat diperluas menjadi Wadah Auto-mocking.

Dalam hal apapun ketika menggunakan Injuktor Konstruktor, Konstruktor harus sederhana jadi tidak ada alasan untuk menguji mereka secara langsung. Mereka adalah rincian implementasi yang terjadi sebagai efek samping dari tes perilaku yang Anda tulis.

Contoh-contoh layanan dapat diedarkan secara tidak perlu, meningkatkan kompleksitas kode.

Setuju, ini tentu kode bau, tapi sekali lagi, bau dalam pelaksanaannya. Konstruktor Injeksi tidak dapat disalahkan untuk ini.

Ketika ini terjadi, itu adalah gejala bahwa a Layanan Fasad hilang. Daripada melakukan ini:

public class BlahHelper {

    // Keep so we can create objects later
    var _service = null;

    public BlahHelper(IMyClass service) {
        _service = service;
    }

    public void DoSomething() {
        var worker = new SpecialPurposeWorker("flag", 100, service);
        var status = worker.DoSomethingElse();
        // ...

    }
}

melakukan hal ini:

public class BlahHelper {
    private readonly ISpecialPurposeWorkerFactory factory;

    public BlahHelper(ISpecialPurposeWorkerFactory factory) {
        this.factory = factory;
    }

    public void DoSomething() {
        var worker = this.factory.Create("flag", 100);
        var status = worker.DoSomethingElse();
        // ...

    }
}

Mengenai solusi yang diajukan

Solusi yang diajukan adalah Service Locator, dan hanya memiliki kerugian dan tidak ada manfaat.


14
2018-02-06 11:40