Apa perbedaan antara menggunakan injeksi ketergantungan dengan wadah dan menggunakan pelacak layanan?

107

Saya mengerti bahwa secara langsung instantiasi dependensi di dalam kelas dianggap praktik buruk. Ini masuk akal karena melakukan hal tersebut dengan ketat memadukan segala sesuatu yang pada gilirannya membuat pengujian menjadi sangat sulit.

Hampir semua kerangka kerja yang saya temui tampaknya mendukung injeksi ketergantungan dengan wadah daripada menggunakan pencari layanan. Keduanya tampaknya mencapai hal yang sama dengan memungkinkan programmer untuk menentukan objek apa yang harus dikembalikan ketika kelas membutuhkan ketergantungan.

Apa perbedaan keduanya? Mengapa saya memilih satu dari yang lain?

tom6025222
sumber
3
Ini telah ditanyakan (dan dijawab) di StackOverflow: stackoverflow.com/questions/8900710/…
Kyralessa
2
Menurut pendapat saya itu sama sekali tidak biasa bagi pengembang untuk menipu orang lain untuk berpikir bahwa pelacak layanan mereka adalah injeksi ketergantungan. Mereka melakukan itu karena ketergantungan injeksi sering dianggap lebih maju.
Gherman
1
Saya terkejut tidak ada yang menyebutkan bahwa dengan Service Locators, lebih sulit untuk mengetahui kapan sumber daya sementara dapat dihancurkan. Saya selalu berpikir itu adalah alasan utama.
Buh Buh

Jawaban:

126

Ketika objek itu sendiri bertanggung jawab untuk meminta dependensinya, sebagai lawan menerima mereka melalui konstruktor, itu menyembunyikan beberapa informasi penting. Ini hanya sedikit lebih baik daripada kasus penggunaan yang sangat ketat newuntuk instantiate dependensinya. Ini mengurangi kopling karena Anda sebenarnya bisa mengubah dependensi yang didapatnya, tetapi ia masih memiliki dependensi yang tidak dapat diguncang: pencari layanan. Itu menjadi hal yang semuanya bergantung.

Wadah yang memasok dependensi melalui argumen konstruktor memberikan kejelasan paling banyak. Kita melihat di depan bahwa suatu objek membutuhkan AccountRepository, dan a PasswordStrengthEvaluator. Saat menggunakan pelacak layanan, informasi itu tidak segera terlihat. Anda akan segera melihat sebuah kasus di mana sebuah objek memiliki, oh, 17 dependensi, dan berkata pada diri sendiri, "Hmm, itu sepertinya sangat banyak. Apa yang terjadi di sana?" Panggilan ke pelacak layanan dapat disebarkan di sekitar berbagai metode, dan bersembunyi di balik logika bersyarat, dan Anda mungkin tidak menyadari bahwa Anda telah menciptakan "kelas Tuhan" - yang melakukan segalanya. Mungkin kelas itu dapat di refactored menjadi 3 kelas kecil yang lebih fokus, dan karenanya lebih dapat diuji.

Sekarang pertimbangkan pengujian. Jika suatu objek menggunakan pelacak layanan untuk mendapatkan dependensinya, kerangka kerja pengujian Anda juga akan membutuhkan pelacak layanan. Dalam pengujian, Anda akan mengonfigurasi pelacak layanan untuk memasok dependensi ke objek yang diuji - mungkin a FakeAccountRepositorydan a VeryForgivingPasswordStrengthEvaluator, dan kemudian menjalankan tes. Tapi itu lebih banyak pekerjaan daripada menentukan ketergantungan pada konstruktor objek. Dan kerangka kerja pengujian Anda juga menjadi tergantung pada lokasi layanan. Ini hal lain yang harus Anda konfigurasi di setiap tes, yang membuat tes menulis kurang menarik.

Cari "Serivce Locator adalah Anti-Pola" untuk artikel Mark Seeman tentang itu. Jika Anda berada di dunia .Net, dapatkan bukunya. Ini sangat bagus.

Carl Raymond
sumber
1
Membaca pertanyaan saya berpikir tentang Adaptive Code via C # oleh Gary McLean Hall, yang sangat sesuai dengan jawaban ini. Ini memiliki beberapa analogi yang baik seperti pencari lokasi menjadi kunci untuk brankas; ketika diserahkan ke kelas, itu dapat menciptakan dependensi sesuka hati yang mungkin tidak mudah dikenali.
Dennis
10
Satu hal yang IMO perlu tambahkan ke constructor supplied dependenciesvs service locatoradalah bahwa yang pertama dapat diverifikasi waktu kompilasi, sedangkan yang terakhir hanya dapat diverifikasi runtime.
andras
2
Selain itu, pelacak layanan tidak mencegah komponen untuk memiliki banyak banyak dependensi. Jika Anda harus menambahkan 10 parameter ke konstruktor Anda, Anda merasa ada sesuatu yang salah dengan kode Anda. Namun, jika Anda hanya membuat 10 panggilan statis ke pencari layanan, mungkin tidak mudah terlihat bahwa Anda memiliki beberapa masalah. Saya telah mengerjakan proyek besar dengan pencari layanan dan ini adalah masalah terbesar. Terlalu mudah untuk membuat hubungan pendek jalur baru daripada duduk santai, merenung, mendesain ulang, dan refactor.
Laju
1
Tentang pengujian - But that's more work than specifying dependencies in the object's constructor.Saya ingin mengajukan keberatan. Dengan pencari lokasi layanan, Anda hanya perlu menentukan 3 dependensi yang sebenarnya Anda perlukan untuk pengujian Anda. Dengan DI berbasis konstruktor Anda perlu menentukan ALL 10 dari mereka, bahkan jika 7 tidak digunakan.
Vilx-
4
@ Vilx- Jika Anda memiliki beberapa parameter konstruktor yang tidak digunakan maka kemungkinan kelas Anda melanggar prinsip Tanggung Jawab Tunggal.
RB.
79

Bayangkan Anda adalah seorang pekerja di sebuah pabrik yang membuat sepatu .

Anda bertanggung jawab untuk merakit sepatu dan karenanya Anda akan membutuhkan banyak hal untuk melakukannya.

  • Kulit
  • Pita pengukur
  • Lem
  • Kuku
  • Palu
  • Gunting
  • Tali sepatu

Dan seterusnya.

Anda sedang bekerja di pabrik dan Anda siap untuk memulai. Anda memiliki daftar instruksi tentang cara melanjutkan, tetapi Anda belum memiliki bahan atau alat apa pun.

Sebuah Layanan Locator seperti Foreman yang dapat membantu Anda mendapatkan apa yang Anda butuhkan.

Anda meminta Service Locator setiap kali Anda membutuhkan sesuatu, dan mereka pergi untuk mencarikannya untuk Anda. Service Locator telah diberi tahu sebelumnya tentang apa yang mungkin Anda tanyakan dan bagaimana menemukannya.

Anda sebaiknya berharap bahwa Anda tidak meminta sesuatu yang tidak terduga. Jika Locator belum diberitahu sebelumnya tentang alat atau bahan tertentu, mereka tidak akan bisa mendapatkannya untuk Anda, dan mereka hanya akan mengangkat bahu kepada Anda.

pencari layanan

Sebuah Dependency Injection (DI) Wadah seperti sebuah kotak besar yang akan diisi dengan segala sesuatu yang setiap orang perlu di awal hari.

Saat pabrik dimulai, Bos Besar yang dikenal sebagai Root Komposisi mengambil wadah dan membagikan semuanya kepada Manajer Lini .

Manajer Lini sekarang memiliki apa yang mereka butuhkan untuk melakukan tugas mereka hari itu. Mereka mengambil apa yang mereka miliki dan memberikan apa yang diperlukan kepada bawahan mereka.

Proses ini berlanjut, dengan ketergantungan mengaliri lini produksi. Akhirnya, sebuah wadah material dan peralatan muncul untuk Mandor Anda.

Mandor Anda sekarang mendistribusikan apa yang Anda butuhkan untuk Anda dan pekerja lain, tanpa Anda bahkan meminta mereka.

Pada dasarnya, segera setelah Anda muncul untuk bekerja, semua yang Anda butuhkan sudah ada di dalam kotak menunggu Anda. Anda tidak perlu tahu apa-apa tentang cara mendapatkannya.

wadah injeksi ketergantungan

Rowan Freeman
sumber
45
Ini adalah deskripsi yang sangat baik tentang apa masing-masing dari 2 hal itu, diagram yang super tampan juga! Tetapi ini tidak menjawab pertanyaan "Mengapa memilih satu dari yang lain"?
Matthieu M.
21
"Kamu lebih baik berharap bahwa kamu tidak meminta sesuatu yang tidak terduga sekalipun. Jika Locator belum diberitahu sebelumnya tentang alat atau bahan tertentu" Tapi itu juga bisa benar dari wadah DI.
Kenneth K.
5
@ Frankhopkins Jika Anda menghilangkan mendaftarkan antarmuka / kelas dengan wadah DI Anda, maka kode Anda akan tetap dikompilasi, dan itu akan segera gagal saat runtime.
Kenneth K.
3
Keduanya sama-sama cenderung gagal, tergantung pada bagaimana mereka diatur. Tetapi dengan wadah DI, Anda lebih cenderung untuk menabrak masalah lebih cepat, ketika kelas X dibangun, daripada nanti, ketika kelas X benar-benar membutuhkan satu ketergantungan. Ini bisa dibilang lebih baik, karena lebih mudah mengalami masalah, menemukannya dan menyelesaikannya. Saya setuju dengan Kenneth, bahwa jawabannya menunjukkan masalah ini hanya ada untuk pencari layanan, yang jelas bukan itu masalahnya.
GolezTrol
3
Jawaban yang bagus, tapi saya pikir itu melewatkan satu hal lain; yaitu, jika Anda bertanya mandor pada setiap tahap untuk gajah, dan ia tidak tahu bagaimana untuk mendapatkan satu, ia akan mendapatkan satu untuk Anda tidak ada pertanyaan baik bertanya meskipun Anda tidak membutuhkannya. Dan seseorang yang mencoba men-debug masalah di lantai (pengembang jika Anda mau) akan bertanya-tanya apakah gajah melakukan di sini di meja ini, sehingga mempersulit mereka untuk berpikir tentang apa yang sebenarnya salah. Sedangkan dengan DI, Anda, kelas pekerja, menyatakan di muka setiap ketergantungan yang Anda butuhkan bahkan sebelum Anda memulai pekerjaan sehingga memudahkan untuk memikirkannya.
Stephen Byrne
10

Beberapa poin tambahan yang saya temukan ketika menjelajahi web:

  • Menyuntikkan dependensi ke konstruktor membuatnya lebih mudah untuk memahami apa yang dibutuhkan kelas. IDE modern akan mengisyaratkan argumen apa yang diterima oleh konstruktor dan tipenya. Jika Anda menggunakan pencari lokasi layanan Anda harus membaca kelas sebelum Anda tahu dependensi mana yang diperlukan.
  • Injeksi ketergantungan tampaknya lebih menganut prinsip "katakan jangan tanya" daripada pencari layanan. Dengan mengamanatkan bahwa dependensi dari tipe tertentu Anda "memberi tahu" dependensi mana yang diperlukan. Tidak mungkin untuk membuat instance kelas tanpa melewati dependensi yang diperlukan. Dengan pelacak layanan, Anda "meminta" layanan dan jika pelacak layanan tidak dikonfigurasi dengan benar, Anda mungkin tidak mendapatkan apa yang diperlukan.
tom6025222
sumber
4

Saya datang terlambat ke pesta ini tapi saya tidak bisa menolak.

Apa perbedaan antara menggunakan injeksi ketergantungan dengan wadah dan menggunakan pelacak layanan?

Terkadang tidak ada sama sekali. Yang membuat perbedaan adalah apa yang tahu tentang apa.

Anda tahu Anda menggunakan pencari lokasi layanan ketika klien mencari ketergantungan tahu tentang wadah. Seorang klien yang mengetahui bagaimana menemukan ketergantungannya, bahkan ketika melalui sebuah wadah untuk mendapatkannya, adalah pola pencari lokasi layanan.

Apakah ini berarti jika Anda ingin menghindari pelacak layanan Anda tidak dapat menggunakan wadah? Tidak. Anda hanya perlu menjaga klien agar tidak mengetahui tentang wadah. Perbedaan utama adalah di mana Anda menggunakan wadah.

Katakanlah Clientkebutuhan Dependency. Wadah tersebut memiliki a Dependency.

class Client { 
    Client() { 
        BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
        this.dependency = (Dependency) beanfactory.getBean("dependency");        
    }
    Dependency dependency;
}

Kami baru saja mengikuti pola pencari lokasi layanan karena Clienttahu cara menemukan Dependency. Tentu itu menggunakan kode keras ClassPathXmlApplicationContexttetapi bahkan jika Anda menyuntikkan bahwa Anda masih memiliki pencari layanan karena Clientpanggilan beanfactory.getBean().

Untuk menghindari pencari layanan, Anda tidak harus meninggalkan wadah ini. Anda hanya perlu memindahkannya Clientsehingga Clienttidak tahu tentang itu.

class EntryPoint { 
    public static void main(String[] args) {
        BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
        Client client = (Client) beanfactory.getBean("client");

        client.start();
    }
}

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="dependency" class="Dependency">
    </bean>

    <bean id="client" class="Client">
        <constructor-arg value="dependency" />        
    </bean>
</beans>

Perhatikan bagaimana Clientsekarang tidak tahu wadah itu ada:

class Client { 
    Client(Dependency dependency) { 

        this.dependency = dependency;        
    }
    Dependency dependency;
}

Pindahkan wadah dari semua klien dan tempelkan di tempat utama di mana ia dapat membuat grafik objek dari semua objek yang berumur panjang. Pilih salah satu objek untuk mengekstrak dan memanggil metode di atasnya dan Anda mulai mencentang seluruh grafik.

Itu memindahkan semua konstruksi statis ke dalam wadah XML namun membuat semua klien Anda tidak tahu bagaimana menemukan dependensi mereka.

Tapi main masih tahu bagaimana menemukan dependensi! Ya itu. Tetapi dengan tidak menyebarkan pengetahuan itu di sekitar Anda telah menghindari masalah inti dari pencari layanan. Keputusan untuk menggunakan wadah sekarang dibuat di satu tempat dan dapat diubah tanpa menulis ulang ratusan klien.

candied_orange
sumber
1

Saya berpikir bahwa cara termudah untuk memahami perbedaan antara keduanya dan mengapa wadah DI jauh lebih baik daripada pelacak layanan adalah dengan memikirkan mengapa kita melakukan inversi ketergantungan pada awalnya.

Kami melakukan inversi dependensi sehingga setiap kelas secara eksplisit menyatakan dengan tepat apa yang menjadi tanggungannya untuk operasi. Kami melakukannya karena ini menciptakan kopling paling longgar yang bisa kita capai. Semakin longgar kopling, semakin mudah untuk menguji dan melakukan refactor (dan umumnya membutuhkan paling sedikit refactoring di masa depan karena kodenya lebih bersih).

Mari kita lihat kelas berikut:

public class MySpecialStringWriter
{
  private readonly IOutputProvider outputProvider;
  public MySpecialFormatter(IOutputProvider outputProvider)
  {
    this.outputProvider = outputProvider;
  }

  public void OutputString(string source)
  {
    this.outputProvider.Output("This is the string that was passed: " + source);
  }
}

Di kelas ini, kami secara eksplisit menyatakan bahwa kami memerlukan IOutputProvider dan tidak ada yang lain untuk membuat kelas ini berfungsi. Ini sepenuhnya dapat diuji dan memiliki ketergantungan pada satu antarmuka. Saya dapat memindahkan kelas ini ke mana saja dalam aplikasi saya, termasuk proyek yang berbeda dan yang diperlukan hanyalah akses ke antarmuka IOutputProvider. Jika pengembang lain ingin menambahkan sesuatu yang baru ke kelas ini, yang memerlukan ketergantungan kedua, mereka harus secara eksplisit tentang apa yang mereka butuhkan dalam konstruktor.

Lihatlah kelas yang sama dengan pencari lokasi:

public class MySpecialStringWriter
{
  private readonly ServiceLocator serviceLocator;
  public MySpecialFormatter(ServiceLocator serviceLocator)
  {
    this.serviceLocator = serviceLocator;
  }

  public void OutputString(string source)
  {
    this.serviceLocator.OutputProvider.Output("This is the string that was passed: " + source);
  }
}

Sekarang saya telah menambahkan locator layanan sebagai ketergantungan. Berikut adalah masalah yang langsung terlihat:

  • Masalah pertama dengan ini adalah dibutuhkan lebih banyak kode untuk mencapai hasil yang sama. Lebih banyak kode buruk. Ini bukan kode yang lebih banyak tetapi masih lebih banyak.
  • Masalah kedua adalah bahwa ketergantungan saya tidak lagi eksplisit . Saya masih perlu menyuntikkan sesuatu ke dalam kelas. Kecuali sekarang hal yang saya inginkan tidak eksplisit. Itu tersembunyi di properti dari hal yang saya minta. Sekarang saya perlu akses ke ServiceLocator dan IOutputProvider jika saya ingin memindahkan kelas ke majelis yang berbeda.
  • Masalah ketiga adalah bahwa ketergantungan tambahan dapat diambil oleh pengembang lain yang bahkan tidak menyadari bahwa mereka mengambilnya ketika mereka menambahkan kode ke kelas.
  • Akhirnya, kode ini lebih sulit untuk diuji (bahkan jika ServiceLocator adalah antarmuka) karena kita harus mengejek ServiceLocator dan IOutputProvider, bukan hanya IOutputProvider

Jadi mengapa kita tidak menjadikan locator layanan sebagai kelas statis? Mari lihat:

public class MySpecialStringWriter
{
  public void OutputString(string source)
  {
    ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
  }
}

Ini jauh lebih sederhana, bukan?

Salah.

Katakanlah IOutputProvider diimplementasikan oleh layanan web yang berjalan sangat lama yang menulis string dalam lima belas database berbeda di seluruh dunia dan membutuhkan waktu yang sangat lama untuk diselesaikan.

Mari kita coba kelas ini. Kami membutuhkan implementasi IOutputProvider yang berbeda untuk pengujian. Bagaimana cara kita menulis tes?

Nah untuk melakukan itu kita perlu melakukan beberapa konfigurasi mewah di kelas ServiceLocator statis untuk menggunakan implementasi IOutputProvider yang berbeda ketika sedang dipanggil oleh tes. Bahkan menulis kalimat itu menyakitkan. Menerapkannya akan menyiksa dan itu akan menjadi mimpi buruk pemeliharaan . Kita seharusnya tidak perlu memodifikasi kelas khusus untuk pengujian, terutama jika kelas itu bukan kelas yang kita coba untuk menguji.

Jadi sekarang Anda pergi dengan a) tes yang menyebabkan perubahan kode mencolok di kelas ServiceLocator yang tidak terkait; atau b) tidak ada tes sama sekali. Dan Anda pergi dengan solusi yang kurang fleksibel juga.

Jadi layanan kelas locator memiliki harus disuntikkan ke konstruktor. Yang berarti bahwa kita pergi dengan masalah khusus yang disebutkan sebelumnya. Pencari layanan memerlukan lebih banyak kode, memberi tahu pengembang lain bahwa ia membutuhkan hal-hal yang tidak diperlukan, mendorong pengembang lain menulis kode yang lebih buruk dan memberi kami lebih sedikit fleksibilitas untuk bergerak maju.

Sederhananya pelacak layanan meningkatkan sambungan dalam aplikasi dan mendorong pengembang lain untuk menulis kode yang sangat berpasangan .

Stephen
sumber
Service Locators (SL) dan Dependency Injection (DI) keduanya memecahkan masalah yang sama dalam perilaku yang sama. Satu-satunya perbedaan jika jika Anda "mengatakan" DI atau "meminta" SL untuk dependensi.
Matthew Whited
@ MatthewWhited Perbedaan utama adalah jumlah dependensi implisit yang Anda ambil dengan pencari lokasi layanan. Dan itu membuat perbedaan besar pada pemeliharaan jangka panjang dan stabilitas kode.
Stephen
Benar-benar tidak. Anda memiliki jumlah dependensi yang sama pula.
Matthew Whited
Awalnya ya, tetapi Anda dapat mengambil dependensi dengan pelacak layanan tanpa menyadari Anda mengambil dependensi. Yang mengalahkan sebagian besar alasan mengapa kita melakukan inversi ketergantungan di tempat pertama. Satu kelas tergantung pada properti A dan B, yang lain tergantung pada B dan C. Tiba-tiba Service Locator menjadi kelas dewa. Wadah DI tidak dan dua kelas benar hanya bergantung pada A dan B dan B dan C masing-masing. Ini membuat refactoring mereka seratus kali lebih mudah. Service Locators berbahaya karena mereka tampak sama dengan DI tetapi sebenarnya tidak.
Stephen