Apakah ServiceLocator anti-pola?


141

Baru-baru ini saya membaca artikel Mark Seemann tentang anti-pola Service Locator.

Penulis menunjukkan dua alasan utama mengapa ServiceLocator merupakan anti-pola:

  1. Masalah penggunaan API (yang saya baik-baik saja)
    Ketika kelas menggunakan pencari Layanan, sangat sulit untuk melihat ketergantungannya karena, dalam banyak kasus, kelas hanya memiliki satu konstruktor PARAMETERLESS. Berbeda dengan ServiceLocator, pendekatan DI secara eksplisit mengekspos dependensi melalui parameter konstruktor sehingga dependensi mudah dilihat di IntelliSense.

  2. Masalah pemeliharaan (yang membuat saya
    bingung ) Perhatikan contoh berikut

Kami memiliki kelas 'MyType' yang menggunakan pendekatan pencari lokasi:

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

Sekarang kami ingin menambahkan ketergantungan lain ke kelas 'MyType'

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Dan di sinilah kesalahpahaman saya dimulai. Penulis berkata:

Menjadi jauh lebih sulit untuk mengetahui apakah Anda memperkenalkan perubahan yang menghancurkan atau tidak. Anda perlu memahami seluruh aplikasi di mana Service Locator digunakan, dan compiler tidak akan membantu Anda.

Tapi tunggu sebentar, jika kita menggunakan pendekatan DI, kita akan memperkenalkan ketergantungan dengan parameter lain dalam konstruktor (dalam kasus injeksi konstruktor). Dan masalahnya akan tetap ada. Jika kita mungkin lupa menyiapkan ServiceLocator, maka kita mungkin lupa menambahkan pemetaan baru dalam wadah IoC kita dan pendekatan DI akan memiliki masalah run-time yang sama.

Juga, penulis menyebutkan tentang kesulitan tes unit. Tapi, bukankah kita memiliki masalah dengan pendekatan DI? Tidakkah kita perlu memperbarui semua tes yang membuat instance kelas itu? Kami akan memperbaruinya agar lulus dependensi baru yang dibuat-buat hanya untuk membuat pengujian kami dapat disusun. Dan saya tidak melihat manfaat apa pun dari pembaruan dan pengeluaran waktu itu.

Saya tidak mencoba mempertahankan pendekatan Service Locator. Tetapi kesalahpahaman ini membuat saya berpikir bahwa saya kehilangan sesuatu yang sangat penting. Bisakah seseorang menghilangkan keraguan saya?

UPDATE (RINGKASAN):

Jawaban untuk pertanyaan saya "Apakah Pencari Lokasi Layanan merupakan anti-pola" sangat bergantung pada keadaan. Dan saya pasti tidak akan menyarankan untuk mencoretnya dari daftar alat Anda. Ini mungkin menjadi sangat berguna ketika Anda mulai berurusan dengan kode warisan. Jika Anda cukup beruntung berada di awal proyek Anda, maka pendekatan DI mungkin merupakan pilihan yang lebih baik karena memiliki beberapa keunggulan dibandingkan Service Locator.

Dan inilah perbedaan utama yang meyakinkan saya untuk tidak menggunakan Service Locator untuk proyek baru saya:

  • Paling jelas dan penting: Service Locator menyembunyikan dependensi kelas
  • Jika Anda menggunakan beberapa kontainer IoC, kemungkinan akan memindai semua konstruktor saat startup untuk memvalidasi semua dependensi dan memberi Anda umpan balik langsung tentang pemetaan yang hilang (atau konfigurasi yang salah); ini tidak mungkin jika Anda menggunakan container IoC Anda sebagai Service Locator

Untuk detailnya, baca jawaban luar biasa yang diberikan di bawah ini.


"Tidakkah kita perlu memperbarui semua tes yang membuat instance kelas itu?" Belum tentu benar jika Anda menggunakan pembuat dalam pengujian Anda. Dalam hal ini, Anda hanya perlu memperbarui pembuatnya.
Peter Karlsson

Anda benar, itu tergantung. Di aplikasi Android besar, misalnya, sejauh ini orang sangat enggan menggunakan DI karena masalah performa pada perangkat seluler dengan spesifikasi rendah. Dalam kasus seperti itu, Anda harus mencari alternatif untuk tetap menulis kode yang dapat diuji, dan menurut saya, Service Locator adalah pengganti yang cukup baik dalam kasus itu. (Catatan: hal-hal dapat berubah untuk Android ketika kerangka kerja Dagger 2.0 DI yang baru cukup matang.)
G. Lombard

1
Perhatikan bahwa karena pertanyaan ini telah diposting, ada pembaruan pada Penunjuk Lokasi Layanan Mark Seemann adalah posting Anti-Pola yang menjelaskan bagaimana pencari layanan melanggar OOP dengan melanggar enkapsulasi, yang merupakan argumen terbaiknya (dan alasan yang mendasari semua gejala yang dia gunakan dalam semua argumen sebelumnya). Pembaruan 2015-10-26: Masalah mendasar dengan Service Locator adalah melanggar enkapsulasi .
NightOwl888

Jawaban:


128

Jika Anda mendefinisikan pola sebagai anti-pola hanya karena ada beberapa situasi di mana ia tidak cocok, maka YA itu pola anti. Tapi dengan alasan itu semua pola juga akan menjadi anti pola.

Sebagai gantinya kita harus melihat apakah ada penggunaan pola yang valid, dan untuk Service Locator ada beberapa kasus penggunaan. Tapi mari kita mulai dengan melihat contoh yang telah Anda berikan.

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Mimpi buruk pemeliharaan dengan kelas tersebut adalah bahwa dependensi disembunyikan. Jika Anda membuat dan menggunakan kelas itu:

var myType = new MyType();
myType.MyMethod();

Anda tidak mengerti bahwa itu memiliki ketergantungan jika disembunyikan menggunakan lokasi layanan. Sekarang, jika kita menggunakan injeksi ketergantungan:

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

Anda dapat langsung melihat dependensi dan tidak dapat menggunakan kelas sebelum memuaskannya.

Dalam garis aplikasi bisnis yang khas, Anda harus menghindari penggunaan lokasi layanan karena alasan itu. Ini harus menjadi pola yang akan digunakan saat tidak ada pilihan lain.

Apakah polanya anti-pola?

Tidak.

Misalnya, pembalikan wadah kontrol tidak akan bekerja tanpa lokasi layanan. Begitulah cara mereka menyelesaikan layanan secara internal.

Tetapi contoh yang jauh lebih baik adalah ASP.NET MVC dan WebApi. Menurut Anda, apa yang memungkinkan injeksi ketergantungan di pengontrol? Benar - lokasi layanan.

Pertanyaan Anda

Tapi tunggu sebentar, jika kita menggunakan pendekatan DI, kita akan memperkenalkan ketergantungan dengan parameter lain dalam konstruktor (dalam kasus injeksi konstruktor). Dan masalahnya akan tetap ada.

Ada dua masalah yang lebih serius:

  1. Dengan lokasi layanan Anda juga menambahkan ketergantungan lain: Pencari lokasi.
  2. Bagaimana Anda mengetahui masa pakai dependensi yang harus dimiliki, dan bagaimana / kapan dependensi harus dibersihkan?

Dengan injeksi konstruktor menggunakan wadah Anda mendapatkannya secara gratis.

Jika kita mungkin lupa menyiapkan ServiceLocator, maka kita mungkin lupa menambahkan pemetaan baru dalam wadah IoC kita dan pendekatan DI akan memiliki masalah run-time yang sama.

Itu benar. Tetapi dengan injeksi konstruktor Anda tidak perlu memindai seluruh kelas untuk mencari tahu dependensi mana yang hilang.

Dan beberapa container yang lebih baik juga memvalidasi semua dependensi saat startup (dengan memindai semua konstruktor). Jadi dengan container tersebut, Anda mendapatkan error runtime secara langsung, dan bukan di beberapa titik temporal selanjutnya.

Juga, penulis menyebutkan tentang kesulitan uji unit. Tapi, bukankah kita akan memiliki masalah dengan pendekatan DI?

Tidak. Karena Anda tidak memiliki ketergantungan ke pencari layanan statis. Sudahkah Anda mencoba mendapatkan pengujian paralel yang bekerja dengan dependensi statis? Ini tidak menyenangkan.


2
Jgauffin, terima kasih atas jawaban Anda. Anda menunjukkan satu hal penting tentang pemeriksaan otomatis saat startup. Saya tidak memikirkannya dan sekarang saya melihat manfaat lain dari DI. Anda juga memberi contoh: "var myType = new MyType ()". Tapi saya tidak bisa menganggapnya valid karena kami tidak pernah membuat instance dependensi di aplikasi nyata (IoC Container melakukannya untuk kami sepanjang waktu). Yaitu: di aplikasi MVC kami memiliki pengontrol, yang bergantung pada IMyService dan MyServiceImpl bergantung pada IMyRepository. Kami tidak akan pernah membuat instance MyRepository dan MyService. Kami mendapatkan contoh dari parameter Ctor (seperti dari ServiceLocator) dan menggunakannya. Bukan?
davidoff

33
Satu-satunya argumen Anda untuk Service Locator bukan anti-pola adalah: "inversi wadah kontrol tidak akan bekerja tanpa lokasi layanan". Namun argumen ini tidak valid, karena Service Locator adalah tentang niat dan bukan tentang mekanik, seperti yang dijelaskan dengan jelas di sini oleh Mark Seemann: "Kontainer DI yang dienkapsulasi di Akar Komposisi bukanlah Penunjuk Lokasi Layanan - ini adalah komponen infrastruktur."
Steven

4
@jgauffin Web API tidak menggunakan Lokasi Layanan untuk DI ke Pengontrol. Itu tidak melakukan DI sama sekali. Apa yang dilakukannya adalah: Ini memberi Anda opsi untuk membuat ControllerActivator Anda sendiri untuk diteruskan ke Layanan Konfigurasi. Dari sana, Anda dapat membuat root komposisi, apakah itu Pure DI, atau Containers. Selain itu, Anda mencampur penggunaan Lokasi Layanan, sebagai komponen pola Anda, dengan definisi "Pola Penunjuk Lokasi Layanan". Dengan definisi tersebut, Komposisi Root DI dapat dianggap sebagai "Pola Penunjuk Lokasi Layanan". Jadi inti dari definisi itu bisa diperdebatkan.
Suamere

1
Saya ingin menunjukkan bahwa ini adalah jawaban yang umumnya bagus, saya hanya mengomentari satu hal menyesatkan yang Anda buat.
Suamere

2
@jgauffin DI dan SL adalah terjemahan versi IoC. SL hanyalah cara yang salah untuk melakukannya. Wadah IoC mungkin SL, atau bisa juga menggunakan DI. Itu tergantung bagaimana kabelnya. Tapi SL itu buruk, buruk buruk. Ini adalah cara untuk menyembunyikan fakta bahwa Anda menggabungkan semuanya dengan erat.
MirroredFate

37

Saya juga ingin menunjukkan bahwa JIKA Anda memfaktorkan ulang kode warisan bahwa pola Pencari Layanan tidak hanya bukan anti-pola, tetapi juga merupakan kebutuhan praktis. Tidak seorang pun akan mengayunkan tongkat ajaib pada jutaan baris kode dan tiba-tiba semua kode itu akan siap. Jadi jika Anda ingin mulai memperkenalkan DI ke basis kode yang sudah ada, sering kali Anda akan mengubah sesuatu menjadi layanan DI secara perlahan, dan kode yang mereferensikan layanan ini seringkali BUKAN menjadi layanan DI. Oleh karena itu, layanan MEREKA perlu menggunakan Penunjuk Lokasi Layanan untuk mendapatkan contoh layanan tersebut yang TELAH dikonversi untuk menggunakan DI.

Jadi ketika refactoring aplikasi warisan yang besar untuk mulai menggunakan konsep DI saya akan mengatakan bahwa Service Locator BUKAN hanya anti-pola, tapi itu adalah satu-satunya cara untuk secara bertahap menerapkan konsep DI ke basis kode.


12
Saat Anda berurusan dengan kode warisan, semuanya dibenarkan untuk mengeluarkan Anda dari kekacauan itu, bahkan jika itu berarti mengambil langkah menengah (dan tidak sempurna). Service Locator adalah langkah perantara tersebut. Ini memungkinkan Anda keluar dari neraka selangkah demi selangkah, selama Anda ingat bahwa ada solusi alternatif yang baik yang didokumentasikan, berulang, dan terbukti efektif . Solusi alternatif ini adalah Injeksi Ketergantungan dan inilah tepatnya mengapa Pencari Lokasi Layanan masih anti-pola; perangkat lunak yang dirancang dengan benar tidak menggunakannya.
Steven

RE: "Saat Anda berurusan dengan kode warisan, semuanya dibenarkan untuk mengeluarkan Anda dari kekacauan itu" Terkadang saya bertanya-tanya apakah hanya ada sedikit kode warisan yang pernah ada, tetapi karena kami dapat membenarkan apa pun untuk memperbaikinya, kami entah bagaimana tidak pernah berhasil melakukannya.
Drew Delano

8

Dari sudut pandang pengujian, Service Locator buruk. Lihat penjelasan bagus Misko Hevery tentang Google Tech Talk dengan contoh kode http://youtu.be/RlfLCWKxHJ0 mulai menit 8:45. Saya menyukai analoginya: jika Anda membutuhkan $ 25, mintalah uang secara langsung daripada memberikan dompet Anda dari mana uang akan diambil. Dia juga membandingkan Service Locator dengan tumpukan jerami yang memiliki jarum yang Anda butuhkan dan tahu cara mengambilnya. Kelas yang menggunakan Service Locator sulit digunakan kembali karena itu.


10
Ini adalah opini daur ulang dan akan lebih baik sebagai komentar. Juga, saya pikir analogi Anda (nya?) Berfungsi untuk membuktikan bahwa beberapa pola lebih sesuai untuk beberapa masalah daripada yang lain.
8bitjunkie

6

Masalah pemeliharaan (yang membuat saya bingung)

Ada 2 alasan berbeda mengapa penggunaan pencari lokasi buruk dalam hal ini.

  1. Dalam contoh Anda, Anda melakukan hard-coding untuk referensi statis ke pencari lokasi ke dalam kelas Anda. Ini secara erat memasangkan kelas Anda secara langsung ke pencari lokasi, yang berarti kelas tidak akan berfungsi tanpa pencari layanan . Selain itu, pengujian unit Anda (dan siapa pun yang menggunakan kelas) juga secara implisit bergantung pada pencari lokasi. Satu hal yang tampaknya tidak diperhatikan di sini adalah bahwa saat menggunakan injeksi konstruktor Anda tidak memerlukan wadah DI saat pengujian unit , yang sangat menyederhanakan pengujian unit Anda (dan kemampuan pengembang untuk memahaminya). Itulah manfaat pengujian unit yang direalisasikan yang Anda dapatkan dari menggunakan injeksi konstruktor.
  2. Adapun mengapa konstruktor Intellisense penting, orang-orang di sini tampaknya telah melewatkan intinya sepenuhnya. Kelas A ditulis sekali, tetapi dapat digunakan di beberapa aplikasi (yaitu, beberapa konfigurasi DI) . Seiring waktu, akan terbayar jika Anda dapat melihat definisi konstruktor untuk memahami dependensi kelas, daripada melihat dokumentasi (semoga up-to-date) atau, jika gagal, kembali ke kode sumber asli (yang mungkin tidak berguna) untuk menentukan dependensi kelas. Kelas dengan pencari lokasi umumnya lebih mudah untuk ditulis , tetapi Anda lebih dari membayar biaya kenyamanan ini dalam pemeliharaan proyek yang berkelanjutan.

Biasa dan sederhana: Kelas dengan pencari lokasi di dalamnya lebih sulit untuk digunakan kembali daripada kelas yang menerima dependensinya melalui konstruktornya.

Pertimbangkan kasus di mana Anda perlu menggunakan layanan dari LibraryAyang pembuatnya memutuskan akan digunakan ServiceLocatorAdan layanan dari LibraryBsiapa penulis memutuskan akan menggunakannya ServiceLocatorB. Kami tidak punya pilihan selain menggunakan 2 pencari lokasi layanan yang berbeda dalam proyek kami. Berapa banyak dependensi yang perlu dikonfigurasi adalah permainan menebak jika kita tidak memiliki dokumentasi yang baik, kode sumber, atau pembuat panggilan cepat. Jika opsi ini gagal, kita mungkin perlu menggunakan decompiler sajauntuk mencari tahu apa saja dependensinya. Kami mungkin perlu mengkonfigurasi 2 API pencari lokasi layanan yang sama sekali berbeda, dan bergantung pada desainnya, mungkin tidak mungkin untuk hanya membungkus kontainer DI Anda yang ada. Mungkin tidak mungkin sama sekali untuk berbagi satu contoh ketergantungan antara dua perpustakaan. Kompleksitas proyek bahkan bisa semakin diperparah jika pencari layanan tidak benar-benar berada di perpustakaan yang sama dengan layanan yang kami butuhkan - kami secara implisit menyeret referensi perpustakaan tambahan ke dalam proyek kami.

Sekarang pertimbangkan dua layanan yang sama yang dibuat dengan injeksi konstruktor. Tambahkan referensi ke LibraryA. Tambahkan referensi ke LibraryB. Berikan dependensi dalam konfigurasi DI Anda (dengan menganalisis apa yang dibutuhkan melalui Intellisense). Selesai.

Mark Seemann memiliki jawaban StackOverflow yang dengan jelas menggambarkan manfaat ini dalam bentuk grafis , yang tidak hanya berlaku saat menggunakan pencari lokasi dari pustaka lain, tetapi juga saat menggunakan default asing dalam layanan.


1

Pengetahuan saya tidak cukup baik untuk menilai ini, tetapi secara umum, saya pikir jika sesuatu memiliki kegunaan dalam situasi tertentu, itu tidak berarti itu tidak bisa menjadi anti-pola. Terutama, ketika Anda berurusan dengan pustaka pihak ketiga, Anda tidak memiliki kendali penuh pada semua aspek dan Anda mungkin akan menggunakan solusi yang bukan yang terbaik.

Berikut adalah paragraf dari Adaptive Code Via C # :

"Sayangnya, pencari lokasi terkadang merupakan anti-pola yang tidak dapat dihindari. Pada beberapa jenis aplikasi — terutama Windows Workflow Foundation - infrastruktur tidak cocok untuk injeksi konstruktor. Dalam kasus ini, satu-satunya alternatif adalah menggunakan pencari lokasi. Ini adalah lebih baik daripada tidak menyuntikkan dependensi sama sekali. Untuk semua kritik saya terhadap pola (anti-), itu jauh lebih baik daripada membangun dependensi secara manual. Lagi pula, itu masih memungkinkan semua titik ekstensi penting yang disediakan oleh antarmuka yang memungkinkan dekorator, adaptor, dan manfaat serupa. "

- Hall, Gary McLean. Kode Adaptif melalui C #: Pengkodean tangkas dengan pola desain dan prinsip SOLID (Referensi Pengembang) (p. 309). Pendidikan Pearson.


0

Penulis beralasan bahwa "kompilator tidak akan membantu Anda" - dan itu benar. Saat Anda berkenan pada sebuah kelas, Anda pasti ingin memilih antarmukanya dengan hati-hati - di antara tujuan lain untuk membuatnya se-independen ... seperti yang masuk akal.

Dengan meminta klien menerima referensi ke layanan (ke dependensi) melalui antarmuka eksplisit, Anda

  • secara implisit mendapatkan pemeriksaan, sehingga kompilator "membantu".
  • Anda juga menghilangkan kebutuhan klien untuk mengetahui sesuatu tentang "Locator" atau mekanisme serupa, sehingga klien sebenarnya lebih mandiri.

Anda benar bahwa DI memiliki masalah / kekurangannya, tetapi keuntungan yang disebutkan sejauh ini lebih besar daripada mereka ... IMO. Anda benar, bahwa dengan DI ada ketergantungan yang diperkenalkan di antarmuka (konstruktor) - tetapi ini mudah-mudahan ketergantungan yang Anda butuhkan dan yang ingin Anda buat terlihat dan dapat diperiksa.


Zrin, terima kasih atas pemikiran Anda. Seperti yang saya pahami, dengan pendekatan DI yang "tepat", saya tidak boleh membuat instance dependensi saya di mana pun kecuali tes unit. Jadi, kompilator hanya akan membantu saya dengan pengujian saya. Tapi seperti yang saya jelaskan dalam pertanyaan awal saya, "bantuan" dengan tes yang rusak ini tidak memberi saya apa-apa. Melakukannya?
davidoff

Argumen 'informasi statis' / 'pemeriksaan waktu kompilasi' sangat menarik. Seperti yang ditunjukkan oleh @davidoff, DI sama-sama rentan terhadap kesalahan waktu proses. Saya juga akan menambahkan bahwa IDE modern menyediakan tampilan tooltip dari informasi komentar / ringkasan untuk anggota, dan bahkan pada mereka yang tidak, seseorang masih akan melihat dokumentasi sampai mereka hanya 'tahu' API. Dokumentasi adalah dokumentasi, apakah parameter konstruktor yang diperlukan atau keterangan tentang cara mengkonfigurasi suatu ketergantungan.
tuespetre

Berpikir tentang implementasi / pengkodean dan jaminan kualitas - keterbacaan kode sangat penting - terutama untuk definisi antarmuka. Jika Anda dapat melakukannya tanpa pemeriksaan waktu kompilasi otomatis dan jika Anda memberikan komentar / mendokumentasikan antarmuka Anda secara memadai, maka saya kira Anda setidaknya dapat memperbaiki sebagian kerugian dari ketergantungan tersembunyi pada semacam variabel global dengan konten yang tidak terlihat / dapat diprediksi dengan mudah. Saya akan mengatakan Anda membutuhkan alasan yang baik untuk menggunakan pola ini yang lebih besar daripada kerugiannya.
Zrin

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.