Bağımlılık Enjeksiyonu: Alan Enjeksiyonu vs Yapıcı Enjeksiyonu?


61

Bunun sıcak bir tartışma olduğunu ve görüşlerin en iyi yaklaşım uygulamasına göre zaman içinde değişme eğiliminde olduğunu biliyorum.

Yapıcı enjeksiyonunun faydaları hakkında farklı bloglar (örneğin: petrikainulainen ve schauderhaft ve fowler ) okumaya başlayana kadar sadece sınıflarım için alan enjeksiyonu kullandım . O zamandan beri, gerekli bağımlılıklar için yapıcı enjeksiyonunu ve isteğe bağlı bağımlılıklar için ayarlayıcı enjeksiyonu kullanmak için metodolojilerimi değiştirdim.

Bununla birlikte, son zamanlarda JMockit'in (alaycı bir çerçeve) kurucusu ve belirleyici enjeksiyonunu kötü bir uygulama olarak gördüğü ve JEE topluluğunun kendisiyle aynı fikirde olduğunu belirttiği tartışmalara girdim .

Günümüz dünyasında, enjeksiyon yapmanın tercih edilen bir yolu var mı? Alan enjeksiyonu tercih ediliyor mu?

Yapımcılığa son birkaç yıl içinde saha enjeksiyonundan geçtikten sonra, kullanımı daha net buluyorum, ancak bakış açımı tekrar incelemem gerekip gerekmediğini merak ediyorum. JMockit (Rogério Liesenfeld) ' nin yazarı DI' de açıkça iyi biliniyor, bu yüzden yapıcı / belirleyici enjeksiyonuna karşı çok kuvvetli hissettiği için yaklaşımımı gözden geçirmek zorundayım.



2
Yapıcı enjeksiyon veya ayarlayıcı enjeksiyon kullanmanıza izin verilmiyorsa , bağımlılıklarınızı sınıfa nasıl vermelisiniz? Belki de Mockit'le olan konuşmanız özel olmadığı sürece tartışmayla bağlantı kursanız iyi olur.
Robert Harvey,

2
@DavidArno: Değişmez bir sınıfta devlet, kurucuda ... bir kez ayarlanır .
Robert Harvey,

1
@Davkd tanımladığınız şey anemik alan modelidir. Kesinlikle savunucuları var ama artık nesneye yönelik değil; Verilerin ve bu verilere etki eden fonksiyonların birlikte yaşadığı yer.
Richard Tingle

3
@David İşlevsel programlamada hiçbir sorun yok. Ancak java temelde nesne yönelimli olacak şekilde tasarlanmıştır ve çok dikkatli değilseniz, sürekli olarak onunla savaşacak ve anemik alan modeliyle sonuçlanacaksınız . İşlev yönelimli kodda yanlış bir şey yok, ancak bunun için uygun bir araç kullanılmalıdır, ki java değildir (lamda'nın işlev tabanlı programlama öğelerini eklenmiş olmasına rağmen)
Richard Tingle

Yanıtlar:


48

Alan enjeksiyonu zevkime göre biraz "uzak mesafeden ürkütücü hareket" .

Google Grupları yayınınıza verdiğiniz örneği göz önünde bulundurun:

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(); }
...
}

Yani temelde söylediğiniz şudur: "Sınıfımın @injectabletümü özel olarak ilan edilmiş olsa bile, devletin dışardan bazı ajanlar tarafından otomatik olarak doldurulabileceği anlamına gelen özel sınıflı bir sınıfım var . "

Bunun için motivasyonları anlıyorum. Düzgün bir sınıf oluşturmada doğuştan gelen törenin çoğundan kaçınma girişimidir. Temel olarak, söylediği şudur: "Tüm bu kazan plakasını yazmaktan bıktım, bu yüzden sadece bütün durumumu ekleyeceğim ve DI konteynerinin benim için ayarlaması için bakmasına izin vereceğim."

Mükemmel derecede geçerli bir bakış açısı. Ancak, tartışılmayacak şekilde çalışılması gereken dil özellikleri için bir geçici çözümdür. Ayrıca, neden orada durdun? Geleneksel olarak DI, bir arkadaşı Arabirimi olan her sınıfa güvenmiştir. Neden tüm bu arabirimleri ek açıklamalarla da ortadan kaldırmıyorsunuz?

Alternatifleri düşünün (bu C # olacak, çünkü daha iyi biliyorum ama muhtemelen Java’da tam bir eşdeğer var):

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

Zaten bu sınıf hakkında bazı şeyler biliyorum. Değişmez bir sınıftır; durum yalnızca kurucuda ayarlanabilir (bu özel durumda referanslar). Ve her şey bir arayüzden kaynaklandığı için, alaylarınızın bulunduğu yerdeki kurucudaki uygulamaları değiştirebilirim.

Artık DI konteynerimin yapması gereken şey, hangi nesnelerin yenileşmesi gerektiğini belirlemek için yapıcıya yansıtmak. Ancak bu yansıma halka açık bir sınıfta birinci sınıf bir şekilde yapılıyor ; yani metadata zaten sınıfın bir parçasıdır, kurucuda bildirilmiş olup, açık amacı sınıfa ihtiyaç duyduğu bağımlılıkları sağlamaktır.

Bu çok fazla kazan olduğunu kabul etti, ancak dilin nasıl tasarlandığı. Ek açıklamalar, dilin içine yerleştirilmiş olması gereken şeyler için kirli bir hack gibi gözüküyor.


Yazınızı yanlış okumadığım sürece, örneğe tam olarak bakmıyorsunuzdur. Uygulamam, VeracodeServiceyazdığınızla neredeyse aynı (Java vs C # da olsa). Bu VeracodeServiceImplTestaslında bir Unit Test sınıfıdır. @InjectableAlanları esas olarak bağlam içine sokulan nesneleri alay. @TestedAlan tanımlanan DI Oluşturucu ile nesne / sınıftır. Saha enjeksiyonunda Yapıcı enjeksiyonunu tercih eden bakış açınıza katılıyorum. Ancak, bahsettiğim gibi, JMockit yazarı tersini hissediyor ve nedenini anlamaya çalışıyorum
Eric B.

Bu tartışmadaki ilk gönderiye bakarsanız, Repo sınıfımda neredeyse tam olarak aynı kırığın olduğunu göreceksiniz.
Eric B.

Sadece test sınıfları hakkında konuşuyorsanız, önemli olmayabilir. Çünkü test sınıfları.
Robert Harvey,

Hayır - test derslerinden bahsetmiyorum. Ben üretim sınıflarından bahsediyorum. Bu sadece tartışma grubundaki örnekte, ünite testi olduğu için, çünkü çerçeve alaycı / test çerçevesi. (Tüm tartışma, alaycı çerçevenin yapıcı enjeksiyonunu desteklemesi gerekip gerekmediğini etrafında döner)
Eric B.

3
+1: Evet, C # sürümünde sihir yok. Standart bir sınıftır, bağımlılıkları vardır, hepsi sınıf kullanılmadan önce bir noktada enjekte edilir. Sınıf bir DI kap ile kullanılabilir veya kullanılamaz. Sınıftaki hiçbir şey IOC veya "Otomatik Bağımlılık Enjeksiyonu" çerçevesini söylemez.
İkili Kurtuluş

27

Daha az test başlatma boiletplate argümanı geçerlidir, ancak dikkate alınması gereken başka endişeler var. Her şeyden önce, bir soruyu cevaplamanız gerekir:

Sınıfımın yalnızca yansıma ile gerçekleştirilebilir olmasını mı istiyorum?

Alan enjeksiyonlarını kullanmak , bir sınıfın yansıma kullanarak nesneleri harekete geçiren ve bu özel enjeksiyon açıklamalarını destekleyen bağımlılık enjeksiyon ortamlarına uyumunu daraltmak anlamına gelir . Java dilini temel alan bazı platformlar yansımayı ( GWT ) bile desteklemez , bu nedenle alana enjekte edilen sınıf onlarla uyumlu olmaz.

İkinci sayı performans . Yapıcı çağrısı (doğrudan veya yansıtma yoluyla) her zaman bir yansıma alanı atamasından çok daha hızlıdır. Bağımlılık enjeksiyon çerçeveleri, bağımlılık ağacı oluşturmak ve yansıma yapıcıları oluşturmak için yansıma analizini kullanmalıdır. Bu işlem ek performansa neden olur.

Performans, sürdürülebilirliği etkiler . Her test paketi bir tür bağımlılık enjeksiyon kabında çalıştırılmalıysa, birkaç bin ünite testinden oluşan bir test çalışması onlarca dakika sürebilir. Bir kod tabanının boyutuna bağlı olarak, bu bir sorun olabilir.

Tüm bunlar birçok yeni soruyu doğuruyor:

  1. Kodun bölümlerini başka bir platformda kullanma olasılığı nedir?
  2. Kaç tane birim testi yapılacak?
  3. Her yeni sürümü hazırlamak için ne kadar hızlı ihtiyacım var?
  4. ...

Genellikle, bir proje ne kadar büyük ve önemli olursa, bu faktörler o kadar önemli olur. Ayrıca, Kalite açısından genel olarak kodun uyumluluk, test edilebilirlik ve bakım kolaylığı konusunda yüksek olmasını isteriz. Felsefi bakış açısından, alan enjeksiyonu , Java'nın ana paradigması olan nesne yönelimli programlamanın dört temelinden biri olan kapsüllemeyi kırar .

Alan enjeksiyonuna karşı birçok, birçok tartışma.


3
+1 Bir sinire çarptın. Bir sınıfı başlatmak için yansıma veya bir DI / IoC çerçevesine ihtiyaç duymayı sevmiyorum. Amsterdam'dan Paris'e gitmek için Roma'ya gitmek gibi. Dikkat et DI'yi seviyorum. Sadece çerçeveler hakkında emin değilim.
Marjan Venema,

1
Harika argümanlar. Kendi düşüncelerimin çoğunu sözlü olarak dile getiriyor ama başkalarının da aynı şekilde hissetmesini görmekten mutluyum. Saha enjeksiyonunu bulduğum kadar şaşırtıcıydı, sanırım "kolay" olduğu için onu çok sevdim. Yapıcı enjeksiyonunu şimdi çok daha net buluyorum.
Eric B.

19

Alan enjeksiyonu benden kesin bir "Hayır" oyu alır.

Robert Harvey gibi, zevkime göre biraz fazla otomatik. Açık kodları örtük yerine tercih ederim ve dolaylı olarak yalnızca kodun anlaşılmasını ve nedenini zorlaştırdığı için açık yararlar sağladığında / toleransı kabul ediyorum.

Maciej Chałapuk gibi, bir sınıfı başlatmak için yansıma veya DI / IoC çerçevesine ihtiyaç duymaktan da hoşlanmıyorum. Amsterdam'dan Paris'e gitmek için Roma'ya gitmek gibi.

Seni seviyorum DI ve onun getirdiği tüm avantajları seviyorum. Sadece bir sınıfı başlatmak için çerçevelere ihtiyaç duyulduğundan emin değilim. Özellikle IoC konteynerlerinin veya diğer DI çerçevelerinin test kodu üzerindeki etkisi yoktur.

Testlerimin çok basit olmasını seviyorum. Sınıfımın teste girmesini sağlamak için bir IoC kabı ya da diğer DI çerçevesini kurma yolunda ilerlemekten hoşlanmıyorum. Sadece garip hissettiriyor.

Ve testlerimi çerçevenin küresel durumuna bağlı kılıyor. Diğer bir deyişle, artık farklı bağımlılıklarla ayarlanacak bir X sınıfı örneği gerektiren paralel olarak iki test yapamam.

En azından yapıcı ve ayarlayıcı enjeksiyonuyla, testlerinizde çerçeveleri ve / veya yansımayı kullanmama seçeneğiniz vardır.


Örneğin, Mockito mockito.org kullanıyorsanız, saha enjeksiyonunu kullanırken bile bir DI / IoC çerçevesine ihtiyacınız yoktur ve testi paralel ve benzeri şekilde çalıştırabilirsiniz.
Hans-Peter Störr 27:15

1
@hstoerr Güzel. Bununla birlikte, bu Mockito'nun yansıma kullandığı anlamına gelmelidir (veya bunun Java eşdeğeri olarak adlandırılırsa) ...
Marjan Venema

5

Peki, bu polemik bir tartışma gibi görünüyor. Ele alınması gereken ilk şey Saha enjeksiyonunun Setter enjeksiyonundan farklı olmasıdır . Zaten Alan ve Setter enjeksiyonunun aynı şey olduğunu düşünen bazı insanlarla çalışıyorum.

Yani, açık olmak gerekirse:

Alan enjeksiyonu:

@Autowired
private SomeService someService;

Ayarlayıcı enjeksiyon:

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

Fakat benim için aynı endişeleri paylaşıyorlar. Ve favorim, yapıcı enjeksiyonu:

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

Yapıcı enjeksiyonunun setter / field enjeksiyonundan daha iyi olduğuna inanmak için aşağıdaki nedenler var (puanımı göstermek için bazı Spring linkleri vereceğim):

  1. Dairesel bağımlılıkları önleme : Bahar, Fasasz'ın açıkladığı gibi, Fasulye arasındaki dairesel bağımlılıkları tespit edebilmektedir. Ayrıca bunu söyleyen Spring Team'e bakın .
  2. Sınıfın bağımlılıkları açıktır : Sınıfın bağımlılıkları yapıcıda açıktır ancak alan / ayarlayıcı enjeksiyonuyla gizlenmiştir . Sınıflarımı alan / ayarlayıcı enjeksiyonlu bir "kara kutu" gibi hissediyorum. Ve (deneyimlerime göre) geliştiricilere, birçok bağımlılıkla birlikte sınıftan rahatsız etme eğilimi var; bu, kurucu enjeksiyon kullanarak sınıfı alay etmeye çalıştığınızda açıktır (bir sonraki maddeye bakınız). Geliştiricilerin canını sıkan bir problemin çözülmesi daha muhtemeldir. Ayrıca, çok fazla bağımlılığı olan bir sınıf muhtemelen Tek Sorumluluk İlkesini (SRP) ihlal ediyor.
  3. Alay etmesi kolay ve güvenilir : Alan enjeksiyonuna kıyasla , bir ünite testinde alay etmek daha kolaydır, çünkü sınıf içinde bildirilen özel Bean'e ulaşmak için herhangi bir bağlam alayına veya yansıtma tekniğine ihtiyacınız yoktur ve sizi kandırmazsınız . Siz sadece sınıfı başlatır ve sahte fasulyeleri geçersiniz. Anlaması basit ve kolaydır.
  4. Kod kalitesi araçları size yardımcı olabilir : Sonar gibi araçlar , sınıfın çok karmaşık hale gelmesi konusunda sizi uyarabilir, çünkü çok fazla parametreye sahip bir sınıf muhtemelen çok karmaşık bir sınıftır ve biraz refaktöre ihtiyaç duyar. Bu araçlar, sınıf Alan / Setter enjeksiyonunu kullanırken aynı sorunu tanımlayamaz.
  5. Daha iyi ve bağımsız kod : Kodunuz@AutoWired arasında daha az yayılmasıyla kodunuz çerçeveye daha az bağımlıdır. Bir projedeki DI çerçevesini basit bir şekilde değiştirme olanağının bunu haklı çıkarmayacağını biliyorum (bunu kim yapıyor, doğru mu?). Ancak bu benim için daha iyi bir kod gösteriyor (Bunu EJB ve aramalarla zor bir şekilde öğrendim ). Ve Spring ile @AutoWiredyapıcı enjeksiyonunu kullanarak ek açıklamaları bile kaldırabilirsiniz .
  6. Daha açıklamalar her zaman doğru cevap değildir : For bazı nedenlerle , gerçekten onlar gerçekten yararlıdır yalnızca ek açıklamaları kullanmayı deneyin.
  7. Enjeksiyonları değişmez yapabilirsiniz . Değişmezlik çok memnuniyet verici bir özelliktir.

Daha fazla bilgi arıyorsanız , Spring Blog’un bu eski (ancak yine de alakalı) makalesini , neden bu kadar ayarlayıcı enjeksiyonunu kullandıklarını ve yapıcı enjeksiyonunu kullanma önerilerini anlatan bir öneriyorum:

setter enjeksiyonu beklediğinizden çok daha fazla kullanılıyor, genel olarak Spring gibi çerçeveler, seter enjeksiyonuyla yapılandırıcı enjeksiyonundan çok daha uygundur.

Ve bu yapıcının neden uygulama kodu için daha uygun olduğuna inandıklarına dair bu not:

Yapıcı enjeksiyonunun uygulama kodu için çerçeve kodu için olduğundan daha kullanışlı olduğunu düşünüyorum. Uygulama kodunda, yapılandırmanız gereken isteğe bağlı değerlere daha az ihtiyaç duyarsınız.

Son düşünceler

Yapıcıların avantajlarından tam olarak emin değilseniz, ayarlayıcıyı (tarla değil) yapıcı enjeksiyonuyla karıştırma fikri, Bahar ekibi tarafından açıklandığı gibi :

Hem Yapıcı hem de Setter tabanlı DI'yi karıştırabileceğinizden, zorunlu bağımlılıklar için yapıcı argümanlarını ve isteğe bağlı bağımlılıklar için ayarlayıcıları kullanmak iyi bir kuraldır.

Ancak, alan enjeksiyonunun muhtemelen üçü arasında kaçınılması gerektiğini unutmayın .


-5

Bağımlılığınız sınıfın ömrü boyunca değişebilir mi? Eğer öyleyse alan / özellik enjeksiyonu kullanın. Aksi halde yapıcı enjeksiyonunu kullanın.


2
Bu gerçekten hiçbir şeyi açıklamıyor. Özel alanları uygun bir şekilde yapabiliyorken neden enjekte ediyorsunuz?
Robert Harvey,

Özel alanlar hakkında nerede bir şey söylüyor? Bir sınıfın halka açık arayüzünden bahsediyorum /
Darren Young

Metod enjeksiyonuna karşı soruda yapıcı enjeksiyona dair bir tartışma yoktur. JMockit'in yazarı her ikisini de kötü olarak görüyor. Sorunun başlığına bakın.
Robert Harvey,

Soru, alan enjeksiyonu vs yapıcı enjeksiyonu değil mi?
Darren Young

1
Alan enjeksiyonu tamamen farklı bir hayvandır. Değerleri özel üyelere atarsınız .
Robert Harvey,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.