Ketergantungan Injeksi: Injeksi Lapangan vs Injeksi Konstruktor?

62

Saya tahu ini adalah debat panas dan opini cenderung berubah seiring waktu tentang praktik pendekatan terbaik.

Saya biasa menggunakan injeksi lapangan khusus untuk kelas saya, sampai saya mulai membaca di blog yang berbeda (mis: petrikainulainen dan schauderhaft dan fowler ) tentang manfaat injeksi konstruktor. Saya telah beralih metodologi saya untuk menggunakan injeksi konstruktor untuk dependensi yang diperlukan dan injeksi setter untuk dependensi opsional.

Namun, saya baru-baru ini terlibat dalam perdebatan dengan penulis JMockit - sebuah kerangka kerja yang mengejek - di mana ia melihat konstruktor & penyetel injeksi sebagai praktik yang buruk dan menunjukkan bahwa komunitas JEE setuju dengannya.

Di dunia sekarang ini, adakah cara yang lebih disukai untuk melakukan injeksi? Apakah injeksi Lapangan lebih disukai?

Setelah beralih ke injeksi konstruktor dari injeksi lapangan dalam beberapa tahun terakhir, saya merasa jauh lebih jelas untuk digunakan, tetapi saya bertanya-tanya apakah saya harus memeriksa kembali sudut pandang saya. Penulis JMockit (Rogério Liesenfeld) , jelas berpengalaman dalam DI, jadi saya merasa berkewajiban untuk meninjau pendekatan saya mengingat dia merasa sangat menentang konstruktor / penyetel injeksi.

Eric B.
sumber
2
Jika Anda tidak diizinkan menggunakan injeksi konstruktor atau injeksi setter, lalu bagaimana Anda menyerahkan dependensi Anda ke kelas? Mungkin sebaiknya Anda menautkan ke debat, kecuali percakapan Anda dengan pria Mockit bersifat pribadi.
Robert Harvey
2
@ Davidvido: Di kelas yang tidak dapat diubah, negara diatur sekali ... di konstruktor.
Robert Harvey
1
@Davkd apa yang Anda gambarkan adalah model domain anemia. Ini tentu saja memiliki pendukung tetapi tidak benar-benar berorientasi objek lagi; tempat data dan fungsi yang bekerja pada data itu hidup bersama.
Richard Tingle
3
@ David Tidak ada yang salah dengan pemrograman fungsional. Tapi java pada dasarnya dirancang untuk berorientasi objek dan Anda akan terus berjuang dan berakhir dengan model domain anemia jika Anda tidak terlalu berhati-hati. Tidak ada yang salah dengan kode berorientasi fungsi tetapi alat yang tepat harus digunakan untuk itu, yang java tidak (meskipun lambda telah menambahkan elemen pemrograman berbasis fungsi)
Richard Tingle

Jawaban:

48

Injeksi lapangan agak terlalu "aksi seram di kejauhan" untuk seleraku.

Pertimbangkan contoh yang Anda berikan di pos Google Groups Anda:

public class VeracodeServiceImplTest {


    @Tested(fullyInitialized=true)
    VeracodeServiceImpl veracodeService;
    @Tested(fullyInitialized=true, availableDuringSetup=true)
    VeracodeRepositoryImpl veracodeRepository;
    @Injectable private ResultsAPIWrapper resultsApiWrapper;
    @Injectable private AdminAPIWrapper adminApiWrapper;
    @Injectable private UploadAPIWrapper uploadApiWrapper;
    @Injectable private MitigationAPIWrapper mitigationApiWrapper;

    static { VeracodeRepositoryImpl.class.getName(); }
...
}

Jadi pada dasarnya apa yang Anda katakan adalah bahwa "Saya memiliki kelas ini dengan negara pribadi, yang saya lampirkan @injectablepenjelasan, yang berarti bahwa negara dapat secara otomatis diisi oleh beberapa agen dari luar, meskipun negara saya semuanya telah dinyatakan pribadi. "

Saya mengerti motivasi untuk ini. Ini adalah upaya untuk menghindari banyak upacara yang melekat dalam mendirikan kelas dengan benar. Pada dasarnya, yang dikatakan orang adalah, "Aku bosan menulis semua pelat ini, jadi aku hanya akan membubuhi keterangan tentang negara bagianku, dan biarkan wadah DI mengurus pengaturannya untukku."

Ini sudut pandang yang benar-benar valid. Tapi itu juga solusi untuk fitur bahasa yang bisa dibilang tidak boleh dikerjakan. Juga, mengapa berhenti di situ? Secara tradisional, DI mengandalkan setiap kelas yang memiliki Interface pendamping. Mengapa tidak menghilangkan semua antarmuka itu dengan anotasi juga?

Pertimbangkan alternatifnya (ini akan menjadi C #, karena saya tahu lebih baik, tetapi mungkin ada padanan yang persis sama di Jawa):

public class VeracodeService
{        
    private readonly IResultsAPIWrapper _resultsApiWrapper;
    private readonly IAdminAPIWrapper _adminApiWrapper;
    private readonly IUploadAPIWrapper _uploadApiWrapper;
    private readonly IMitigationAPIWrapper _mitigationApiWrapper;

    // Constructor
    public VeracodeService(IResultsAPIWrapper resultsApiWrapper, IAdminAPIWrapper adminApiWrapper, IUploadAPIWrapper uploadApiWrapper,         IMitigationAPIWrapper mitigationApiWrapper)
    {
         _resultsAPIWrapper = resultsAPIWrapper;
         _adminAPIWrapper = adminAPIWrapper;
         _uploadAPIWrapper = uploadAPIWrapper;
         _mitigationAPIWrapper = mitigationAPIWrapper;
    }
}

Sudah saya tahu beberapa hal tentang kelas ini. Ini adalah kelas yang tidak berubah; negara hanya dapat diatur dalam konstruktor (referensi, dalam kasus khusus ini). Dan karena semuanya berasal dari sebuah antarmuka, saya bisa menukar implementasi di dalam konstruktor, yang merupakan tempat mengejek Anda.

Sekarang semua yang harus dilakukan wadah DI saya adalah merefleksikan konstruktor untuk menentukan objek apa yang diperlukan untuk memulai. Tapi refleksi itu dilakukan pada anggota publik, dengan cara kelas satu; yaitu metadata sudah menjadi bagian dari kelas, yang telah dinyatakan dalam konstruktor, sebuah metode yang tujuan ekspresinya adalah untuk menyediakan kelas dengan dependensi yang dibutuhkannya.

Memang ini banyak boilerplate, tetapi ini adalah bagaimana bahasa dirancang. Anotasi tampak seperti peretasan kotor untuk sesuatu yang seharusnya sudah ada dalam bahasa itu sendiri.

Robert Harvey
sumber
Kecuali saya salah membaca posting Anda, Anda tidak benar-benar melihat contoh dengan benar. Implementasi saya VeracodeServicehampir identik dengan yang Anda tulis (meskipun dalam Java vs C #). The VeracodeServiceImplTestsebenarnya kelas Unit Uji. Para @Injectablebidang dasarnya mengejek benda yang dimasukkan ke dalam konteks. The @Testedlapangan adalah objek / kelas dengan DI Constructor didefinisikan. Saya setuju dengan sudut pandang Anda yang lebih suka injeksi Konstruktor daripada injeksi lapangan. Namun, seperti yang saya sebutkan, penulis JMockit merasakan hal yang sebaliknya dan saya mencoba memahami mengapa
Eric B.
Jika Anda melihat posting pertama dalam diskusi itu, Anda akan melihat saya memiliki defn yang hampir sama persis di kelas Repo saya.
Eric B.
Jika Anda hanya berbicara tentang kelas tes, itu mungkin tidak masalah. Karena, kelas tes.
Robert Harvey
Tidak - Saya tidak berbicara tentang kelas tes. Saya berbicara tentang kelas produksi. Kebetulan bahwa contoh dalam kelompok diskusi adalah tes unit, karena kerangka kerja adalah kerangka mengejek / pengujian. (Seluruh diskusi berputar jika kerangka kerja mengejek harus mendukung injeksi konstruktor)
Eric B.
3
+1: Ya, tidak ada keajaiban dalam versi C #. Ini kelas standar, memiliki dependensi, mereka semua disuntikkan pada satu titik, sebelum kelas digunakan. Kelas dapat digunakan dengan wadah DI, atau tidak. Tidak ada di kelas mengatakan itu kerangka kerja IOC atau "Injeksi Ketergantungan Otomatis".
Binary Worrier
27

Argumen kurang uji inisialisasi boiletplate valid, tetapi ada kekhawatiran lain yang harus diperhitungkan. Pertama-tama, Anda harus menjawab pertanyaan:

Apakah saya ingin kelas saya hanya instantiable dengan refleksi?

Menggunakan injeksi lapangan berarti mempersempit kompatibilitas kelas ke lingkungan injeksi ketergantungan yang membuat objek menggunakan refleksi dan mendukung anotasi injeksi khusus ini. Beberapa platform berbasis bahasa Java bahkan tidak mendukung refleksi ( GWT ), sehingga kelas yang diinjeksi lapangan tidak akan kompatibel dengan mereka.

Masalah kedua adalah kinerja . Panggilan konstruktor (langsung atau dengan refleksi) selalu lebih cepat daripada banyak tugas bidang refleksi. Kerangka kerja injeksi ketergantungan harus menggunakan analisis refleksi untuk membangun pohon dependensi dan membuat konstruktor refleksi. Proses ini menyebabkan hit kinerja tambahan.

Kinerja memengaruhi kemampuan pemeliharaan . Jika setiap rangkaian uji harus dijalankan dalam semacam wadah injeksi ketergantungan, serangkaian uji coba beberapa ribu unit dapat berlangsung puluhan menit. Bergantung pada ukuran basis kode, ini mungkin masalah.

Ini semua menimbulkan banyak pertanyaan baru:

  1. Berapa probabilitas menggunakan bagian-bagian kode pada platform lain?
  2. Berapa banyak tes unit yang akan ditulis?
  3. Seberapa cepat saya perlu menyiapkan setiap rilis baru?
  4. ...

Secara umum, semakin besar dan semakin penting suatu proyek, semakin signifikan faktor-faktor ini. Juga, Dari segi kualitas, kami umumnya ingin menjaga kode tinggi pada kompatibilitas, testability dan rawatan. Dari sudut pandang filosofis, injeksi lapangan memecah enkapsulasi , yang merupakan salah satu dari empat dasar pemrograman berorientasi objek , yang merupakan paradigma utama Jawa.

Banyak, banyak argumen menentang injeksi lapangan.

Maciej Chałapuk
sumber
3
+1 Anda menekan saraf. Saya tidak suka membutuhkan refleksi atau kerangka DI / IoC untuk membuat instance kelas. Ini seperti mengemudi ke Roma untuk sampai ke Paris dari Amsterdam. Pikiran Anda, saya suka DI. Hanya tidak yakin tentang kerangka kerjanya.
Marjan Venema
1
Argumen hebat. Itu memverbalisasi banyak pemikiran yang saya miliki sendiri tetapi saya senang melihat orang lain merasakan hal yang sama. Luar biasa seperti yang saya temukan di injeksi lapangan, saya pikir saya sangat menyukainya hanya karena itu "lebih mudah". Saya menemukan injeksi konstruktor jauh lebih jelas sekarang.
Eric B.
19

Injeksi lapangan mendapat suara "Tidak" yang pasti dari saya.

Seperti Robert Harvey, itu agak terlalu otomatis untuk seleraku. Saya lebih suka kode eksplisit daripada implisit, dan mentolerir tipuan hanya ketika memberikan manfaat yang jelas karena membuat kode lebih sulit untuk dipahami dan dipikirkan.

Seperti Maciej Chałapuk, saya tidak suka membutuhkan refleksi atau kerangka kerja DI / IoC untuk membuat instance kelas. Ini seperti mengemudi ke Roma untuk sampai ke Paris dari Amsterdam.

Pikiran Anda, saya suka DI dan semua keuntungan yang dibawanya. Hanya tidak yakin tentang membutuhkan kerangka kerja untuk membuat kelas. Terutama bukan efek bahwa wadah IoC atau kerangka kerja DI lainnya dapat memiliki pada kode uji.

Saya suka tes saya sangat mudah. Saya tidak suka harus melalui tipu daya menyiapkan wadah IOC atau kerangka kerja DI lainnya untuk kemudian membuat mereka mengatur kelas saya yang sedang diuji. Rasanya canggung.

Dan itu membuat tes saya tergantung pada keadaan global kerangka kerja. Artinya saya tidak bisa lagi menjalankan dua tes secara paralel yang membutuhkan instance kelas X untuk diatur dengan dependensi yang berbeda.

Setidaknya dengan konstruktor dan injeksi setter, Anda memiliki pilihan untuk tidak menggunakan kerangka kerja dan / atau refleksi dalam pengujian Anda.

Marjan Venema
sumber
Jika Anda menggunakan, misalnya, Mockito mockito.org , Anda tidak memerlukan kerangka DI / IoC bahkan ketika menggunakan injeksi lapangan, dan dapat menjalankan tes secara paralel dan sebagainya.
Hans-Peter Störr
1
@ hstoerr Bagus. Namun, itu harus berarti bahwa Mockito menggunakan refleksi (atau apa pun yang setara dengan Jawa yang disebut) ...
Marjan Venema
5

Nah, ini sepertinya diskusi polemik. Hal pertama yang perlu diatasi adalah bahwa injeksi Field berbeda dari injeksi Setter . Saya sudah bekerja dengan beberapa orang yang berpikir injeksi Field dan Setter adalah hal yang sama.

Jadi, untuk menjadi jelas:

Injeksi lapangan:

@Autowired
private SomeService someService;

Injeksi setter:

@Autowired
public void setSomeService(SomeService someService) {
    this.someService = someService;
}

Tetapi, bagi saya, mereka memiliki keprihatinan yang sama. Dan, favorit saya, injeksi konstruktor:

@AutoWired
public MyService(SomeService someService) {
    this.someService = someService;
}

Saya memiliki alasan berikut untuk meyakini bahwa injeksi konstruktor lebih baik daripada injeksi setter / bidang (saya akan mengutip beberapa tautan Spring untuk menunjukkan maksud saya):

  1. Mencegah dependensi sirkular : Pegas dapat mendeteksi dependensi melingkar antara Kacang, seperti yang dijelaskan @Tomasz. Lihat juga Tim Musim Semi mengatakan itu.
  2. Ketergantungan kelas jelas : Ketergantungan kelas jelas dalam konstruktor tetapi disembunyikan dengan injeksi bidang / penyetel. Saya merasa kelas saya seperti "kotak hitam" dengan injeksi bidang / penyetel. Dan ada (menurut pengalaman saya) kecenderungan untuk pengembang tidak peduli tentang kelas dengan banyak dependensi, yang jelas ketika Anda mencoba mengejek kelas menggunakan injeksi konstruktor (lihat item berikutnya). Masalah yang mengganggu pengembang kemungkinan besar akan diselesaikan. Juga, kelas dengan terlalu banyak dependensi mungkin melanggar Prinsip Tanggung Jawab Tunggal (SRP).
  3. Mudah dan dapat diandalkan untuk mengejek : dibandingkan dengan injeksi Lapangan , lebih mudah untuk mengejek dalam tes unit, karena Anda tidak memerlukan konteks tiruan atau teknik refleksi untuk mencapai Kacang pribadi yang dinyatakan di dalam kelas dan Anda tidak akan tertipu olehnya . Anda hanya instantiate kelas dan lulus kacang mengejek. Mudah dimengerti dan mudah.
  4. Alat-alat berkualitas kode dapat membantu Anda : Alat-alat seperti Sonar dapat memperingatkan Anda tentang kelas yang terlalu kompleks, karena kelas dengan banyak parameter mungkin kelas yang terlalu rumit dan perlu refactor. Alat ini tidak dapat mengidentifikasi masalah yang sama ketika kelas menggunakan injeksi Field / Setter.
  5. Kode yang lebih baik dan independen : Dengan @AutoWiredpenyebaran yang lebih sedikit di antara kode Anda, kode Anda kurang tergantung pada kerangka kerja. Saya tahu bahwa kemungkinan untuk secara sederhana mengubah kerangka kerja DI dalam suatu proyek tidak membenarkan hal itu (siapa yang melakukan itu, bukan?). Tapi ini menunjukkan, bagi saya, kode yang lebih baik (saya belajar ini dengan cara yang sulit dengan EJB dan pencarian). Dan dengan Spring Anda bahkan dapat menghapus @AutoWiredanotasi menggunakan injeksi konstruktor.
  6. Lebih banyak anotasi tidak selalu merupakan jawaban yang tepat : Untuk beberapa alasan , saya benar-benar mencoba menggunakan anotasi hanya ketika mereka sangat berguna.
  7. Anda dapat membuat suntikan tidak berubah . Kekekalan adalah fitur yang sangat disambut .

Jika Anda mencari informasi lebih lanjut, saya merekomendasikan artikel lama ini (tapi masih relevan) dari Spring Blog yang memberi tahu kami mengapa mereka menggunakan begitu banyak setter injeksi dan rekomendasi untuk menggunakan injeksi konstruktor:

setter injeksi lebih sering digunakan daripada yang Anda harapkan, adalah kenyataan bahwa kerangka kerja seperti Spring pada umumnya, jauh lebih cocok untuk dikonfigurasikan dengan injeksi setter daripada dengan injeksi konstruktor

Dan catatan ini tentang mengapa mereka percaya bahwa konstruktor lebih cocok untuk kode aplikasi:

Saya pikir injeksi konstruktor jauh lebih dapat digunakan untuk kode aplikasi daripada kode kerangka kerja. Dalam kode aplikasi, Anda secara inheren memiliki kebutuhan lebih rendah untuk nilai-nilai opsional yang perlu Anda konfigurasi

Pikiran terakhir

Jika Anda tidak benar-benar yakin tentang keuntungan konstruktor, mungkin ide untuk mencampurkan setter (bukan bidang) dengan injeksi konstruktor adalah opsi, seperti yang dijelaskan oleh tim Spring :

Karena Anda dapat mencampur keduanya, DI berbasis Constructor dan Setter, adalah aturan praktis yang baik untuk menggunakan argumen konstruktor untuk dependensi wajib dan seter untuk dependensi opsional

Tapi ingat injeksi lapangan, mungkin, yang harus dihindari di antara ketiganya.

Dherik
sumber
-5

Apakah ketergantungan Anda cenderung berubah selama masa kelas? Jika demikian gunakan bidang / injeksi properti. Kalau tidak, gunakan injeksi konstruktor.

Darren Young
sumber
2
Ini tidak menjelaskan apa-apa. Mengapa menyuntikkan bidang pribadi saat Anda bisa melakukannya dengan cara yang benar?
Robert Harvey
Di mana ia mengatakan sesuatu tentang bidang pribadi? Saya sedang berbicara tentang antarmuka publik kelas /
Darren Young
Tidak ada argumen tentang injeksi metode vs injeksi konstruktor dalam pertanyaan. Penulis JMockit melihat keduanya sebagai buruk. Lihatlah judul pertanyaannya.
Robert Harvey
Bukankah pertanyaannya mengatakan injeksi bidang vs injeksi konstruktor?
Darren Young
1
Injeksi lapangan adalah hewan yang sama sekali berbeda. Anda memasukkan nilai ke anggota pribadi .
Robert Harvey