Bagaimana menghindari Ketergantungan konstruktor kegilaan?

300

Saya menemukan bahwa konstruktor saya mulai terlihat seperti ini:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

dengan daftar parameter yang semakin meningkat. Karena "Kontainer" adalah wadah injeksi ketergantungan saya, mengapa saya tidak bisa melakukan ini saja:

public MyClass(Container con)

untuk setiap kelas? Apa kerugiannya? Jika saya melakukan ini, rasanya saya menggunakan statis yang dimuliakan. Silakan bagikan pemikiran Anda tentang kegilaan IoC dan Dependency Injection

JP Richardson
sumber
64
mengapa kamu melewati wadah? Saya pikir Anda mungkin salah paham tentang IOC
Paul Creasey
33
Jika konstruktor Anda menuntut lebih atau lebih parameter, Anda mungkin melakukan terlalu banyak di kelas-kelas itu.
Austin Salonen
38
Itu bukan cara Anda melakukan injeksi konstruktor. Objek tidak tahu sama sekali tentang wadah IoC, begitu pula mereka.
duffymo
Anda bisa membuat konstruktor kosong, di mana Anda memanggil DI langsung meminta barang yang Anda butuhkan. Itu akan menghapus konstruktor madnes tetapi Anda perlu memastikan Anda menggunakan DI Interface .. jika Anda mengubah sistem DI Anda setengah jalan pengembangan. Jujur .. tidak ada yang akan kembali melakukannya dengan cara ini, meskipun inilah yang DI lakukan untuk menyuntikkan konstruktor Anda. doh
Piotr Kula

Jawaban:

409

Anda benar bahwa jika Anda menggunakan wadah sebagai Pencari Layanan, ini lebih atau kurang merupakan pabrik statis yang dimuliakan. Untuk banyak alasan saya menganggap ini sebagai anti-pola .

Salah satu manfaat luar biasa dari Injeksi Konstruktor adalah membuat pelanggaran terhadap Prinsip Tanggung Jawab Tunggal menjadi sangat jelas.

Ketika itu terjadi, saatnya untuk refactor ke Layanan Fasad . Singkatnya, buat antarmuka baru yang lebih berbutir kasar yang menyembunyikan interaksi antara beberapa atau semua dependensi berbutir halus yang saat ini Anda butuhkan.

Mark Seemann
sumber
8
+1 untuk mengukur upaya refactoring menjadi satu konsep; luar biasa :)
Ryan Emerle
46
nyata? Anda baru saja membuat tipuan untuk memindahkan parameter tersebut ke kelas lain, tetapi mereka masih ada di sana! hanya lebih rumit untuk berurusan dengan mereka.
diterima
23
@irreputable: Dalam kasus degenerasi di mana kami memindahkan semua dependensi ke dalam Layanan Agregat, saya setuju bahwa itu hanyalah tingkat tipuan yang tidak membawa manfaat, jadi pilihan kata-kata saya sedikit salah. Namun, intinya adalah bahwa kami hanya memindahkan sebagian dari dependensi berbutir halus ke dalam Layanan Agregat. Ini membatasi jumlah permutasi ketergantungan pada Layanan Agregat yang baru dan untuk dependensi yang ditinggalkan. Ini membuat keduanya lebih mudah untuk dihadapi.
Mark Seemann
92
Komentar terbaik: "Salah satu manfaat luar biasa dari Injeksi Konstruktor adalah membuat pelanggaran terhadap Prinsip Tanggung Jawab Tunggal sangat jelas."
Igor Popov
2
@ DonBox Dalam hal ini Anda dapat menulis implementasi objek nol untuk menghentikan rekursi. Bukan yang Anda butuhkan, tetapi intinya adalah bahwa Constructor Injection tidak mencegah siklus - itu hanya membuat jelas bahwa mereka ada di sana.
Mark Seemann
66

Saya tidak berpikir konstruktor kelas Anda harus memiliki referensi ke periode kontainer IOC Anda. Ini mewakili ketergantungan yang tidak perlu antara kelas Anda dan wadah (jenis ketergantungan yang coba dihindari oleh IOC!).

penurunan
sumber
+1 Bergantung pada wadah IoC membuatnya sulit untuk mengubah wadah itu nanti tanpa mengubah banyak kode di semua kelas lainnya
Tseng
1
Bagaimana Anda menerapkan IOC tanpa memiliki parameter antarmuka pada konstruktor? Apakah saya salah membaca posting Anda?
J Hunt
@J Hunt, saya tidak mengerti komentar Anda. Bagi saya, parameter antarmuka berarti parameter yang merupakan antarmuka untuk dependensi, yaitu, jika wadah injeksi dependensi Anda diinisialisasi MyClass myClass = new MyClass(IDependency1 interface1, IDependency2 interface2)(parameter antarmuka). Ini tidak terkait dengan pos derivasi @, yang saya tafsirkan sebagai mengatakan wadah injeksi dependensi tidak boleh menyuntikkan dirinya ke objeknya, yaitu,MyClass myClass = new MyClass(this)
John Doe
25

Kesulitan melewati parameter tidak menjadi masalah. Masalahnya adalah kelas Anda melakukan terlalu banyak, dan harus dipecah lagi.

Ketergantungan Injeksi dapat bertindak sebagai peringatan dini untuk kelas yang terlalu besar, khususnya karena meningkatnya rasa sakit lulus di semua dependensi.

Kyoryu
sumber
44
Perbaiki saya jika saya salah, tetapi pada titik tertentu Anda harus 'merekatkan semuanya', dan karenanya Anda harus mendapatkan lebih dari beberapa dependensi untuk itu. Misalnya di lapisan Lihat, saat membuat templat dan data untuk mereka, Anda harus mengambil semua data dari berbagai dependensi (misalnya 'layanan') dan kemudian memasukkan semua data ini ke templat dan ke layar. Jika halaman web saya memiliki 10 'blok' informasi yang berbeda, maka saya perlu 10 kelas berbeda untuk memberi saya data itu. Jadi saya perlu 10 dependensi ke dalam kelas Tampilan / Templat saya?
Andrew
4

Saya menemukan pertanyaan serupa tentang Injeksi ketergantungan berbasis konstruktor dan betapa rumitnya untuk melewati semua dependensi.

Salah satu pendekatan, saya telah digunakan di masa lalu adalah dengan menggunakan pola fasad aplikasi menggunakan lapisan layanan. Ini akan memiliki API kasar. Jika layanan ini tergantung pada repositori, Ini akan menggunakan injeksi setter dari properti pribadi. Ini membutuhkan pembuatan pabrik abstrak dan memindahkan logika menciptakan repositori ke pabrik.

Kode lengkap dengan penjelasan dapat ditemukan di sini

Praktik terbaik untuk IoC di lapisan layanan yang kompleks

samsur
sumber
3

Saya membaca seluruh utas ini, dua kali, dan saya pikir orang merespons dengan apa yang mereka ketahui, bukan dengan apa yang diminta.

Pertanyaan asli JP terlihat seperti dia membangun objek dengan mengirimkan resolver, dan kemudian banyak kelas, tapi kami mengasumsikan bahwa kelas / objek itu sendiri adalah layanan, siap untuk injeksi. Bagaimana jika tidak?

JP, jika Anda ingin memanfaatkan DI dan menginginkan kemuliaan pencampuran injeksi dengan data kontekstual, tidak satu pun dari pola-pola ini (atau yang diduga "anti-pola") yang secara khusus mengatasinya. Itu sebenarnya bermuara pada menggunakan paket yang akan mendukung Anda dalam upaya seperti itu.

Container.GetSevice<MyClass>(someObject1, someObject2)

... format ini jarang didukung. Saya percaya kesulitan pemrograman dukungan seperti itu, ditambahkan ke kinerja menyedihkan yang akan dikaitkan dengan implementasi, membuatnya tidak menarik bagi pengembang opensource.

Tapi itu harus dilakukan, karena saya harus dapat membuat dan mendaftarkan pabrik untuk MyClass'es, dan pabrik itu harus dapat menerima data / input yang tidak didorong menjadi "layanan" hanya demi melewati data. Jika "anti-pola" adalah tentang konsekuensi negatif, maka memaksakan keberadaan jenis layanan buatan untuk meneruskan data / model tentu negatif (setara dengan perasaan Anda tentang membungkus kelas Anda ke dalam wadah. Naluri yang sama berlaku).

Ada beberapa kerangka kerja yang mungkin membantu, meskipun mereka terlihat agak jelek. Misalnya, Ninject:

Membuat instance menggunakan Ninject dengan parameter tambahan di konstruktor

Itu untuk .NET, populer, dan masih tidak bersih seperti seharusnya, tapi saya yakin ada sesuatu dalam bahasa apa pun yang Anda pilih untuk digunakan.

Craig Brunetti
sumber
3

Menyuntikkan wadah adalah jalan pintas yang akhirnya akan Anda sesali.

Injeksi yang berlebihan bukan masalahnya, biasanya merupakan gejala dari kelemahan struktural lainnya, terutama pemisahan kekhawatiran. Ini bukan satu masalah tetapi dapat memiliki banyak sumber dan apa yang membuat ini sangat sulit untuk diperbaiki adalah bahwa Anda harus berurusan dengan semuanya, kadang-kadang pada saat yang sama (pikirkan untuk melepaskan spageti).

Berikut adalah daftar hal-hal yang tidak perlu diwaspadai

Desain Domain Buruk (Agregat root .... dll.)

Pemisahan keprihatinan yang buruk (Komposisi layanan, Perintah, pertanyaan) Lihat CQRS dan Event Sourcing.

ATAU Pemetaan (hati-hati, hal-hal ini dapat membawa Anda ke masalah)

Lihat Model dan DTO lainnya (Jangan pernah gunakan kembali, dan cobalah untuk meminimalkannya !!!!)

Marius
sumber
2

Masalah:

1) Konstruktor dengan daftar parameter yang semakin meningkat.

2) Jika kelas diwariskan (Contoh:) RepositoryBasemaka mengubah tanda tangan konstruktor menyebabkan perubahan dalam kelas turunan.

Solusi 1

Lulus IoC Containerke konstruktor

Mengapa

  • Tidak ada lagi peningkatan daftar parameter
  • Tanda tangan konstruktor menjadi sederhana

Kenapa tidak

  • Membuat kelas Anda tergabung erat dengan wadah IoC. (Itu menyebabkan masalah ketika 1. Anda ingin menggunakan kelas itu dalam proyek lain di mana Anda menggunakan wadah IoC yang berbeda. 2. Anda memutuskan untuk mengubah wadah IoC)
  • Membuat kelas Anda kurang deskriptif. (Anda tidak dapat benar-benar melihat konstruktor kelas dan mengatakan apa yang dibutuhkan untuk berfungsi.)
  • Kelas dapat mengakses semua layanan yang berpotensi.

Solusi 2

Buat kelas yang mengelompokkan semua layanan dan meneruskannya ke konstruktor

 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

Kelas turunan

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

Mengapa

  • Menambahkan ketergantungan baru ke kelas tidak memengaruhi kelas turunan
  • Kelas agnostik dari wadah IoC
  • Kelas bersifat deskriptif (dalam aspek ketergantungannya). Dengan konvensi, jika Anda ingin tahu di mana kelas Abergantung, informasi itu diakumulasikanA.Dependency
  • Tanda tangan konstruktor menjadi sederhana

Kenapa tidak

  • perlu membuat kelas tambahan
  • pendaftaran layanan menjadi rumit (Anda harus mendaftar X.Dependencysecara terpisah)
  • Secara konseptual sama dengan passing IoC Container
  • ..

Solusi 2 hanyalah mentah, jika ada argumen yang kuat menentangnya, maka komentar deskriptif akan dihargai

tchelidze
sumber
1

Ini adalah pendekatan yang saya gunakan

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

Berikut ini adalah pendekatan kasar bagaimana melakukan injeksi dan menjalankan konstruktor setelah menyuntikkan nilai. Ini adalah program yang berfungsi penuh.

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

Saat ini saya sedang mengerjakan proyek hobi yang berfungsi seperti ini https://github.com/Jokine/ToolProject/tree/Core

Ronijo
sumber
-8

Kerangka kerja injeksi ketergantungan apa yang Anda gunakan? Sudahkah Anda mencoba menggunakan injeksi berbasis setter saja?

Manfaat untuk injeksi berbasis konstruktor adalah terlihat alami untuk programmer Java yang tidak menggunakan kerangka kerja DI. Anda perlu 5 hal untuk menginisialisasi kelas maka Anda memiliki 5 argumen untuk konstruktor Anda. The downside adalah apa yang Anda perhatikan, itu menjadi sulit ketika Anda memiliki banyak ketergantungan.

Dengan Spring, Anda bisa meneruskan nilai yang diperlukan dengan setter dan Anda bisa menggunakan @required annotations untuk memastikan bahwa mereka disuntikkan. The downside adalah bahwa Anda perlu memindahkan kode inisialisasi dari konstruktor ke metode lain dan memiliki panggilan Spring yang setelah semua dependensi disuntikkan dengan menandainya dengan @PostConstruct. Saya tidak yakin tentang kerangka kerja lain tetapi saya menganggap mereka melakukan sesuatu yang serupa.

Kedua cara bekerja, ini masalah preferensi.

David W Crook
sumber
21
Alasan injeksi konstruktor adalah untuk membuat dependensi menjadi jelas, bukan karena terlihat lebih alami untuk pengembang java.
L-Four
8
Komentar yang terlambat, tetapi jawaban ini membuat saya tertawa :)
Frederik Prijck
1
+1 untuk injeksi berbasis setter. Jika saya memiliki layanan dan repositori yang didefinisikan di kelas saya itu sangat jelas mereka adalah dependensi .. Saya tidak perlu menulis konstruktor VB6 yang besar, dan melakukan kode penempatan yang bodoh di konstruktor. Cukup jelas apa dependensi pada bidang yang diperlukan.
Piotr Kula
Per 2018, Spring secara resmi merekomendasikan untuk tidak menggunakan injeksi setter kecuali untuk dependensi yang memiliki nilai default yang masuk akal. Seperti dalam, jika ketergantungan wajib untuk kelas, injeksi konstruktor dianjurkan. Lihat diskusi tentang setter vs aktor DI
John Doe