9.1 Pembalikan Ketergantungan
Ingat, kami pernah mengatakan bahwa dalam aplikasi server Anda tidak bisa hanya membuat aliran melalui new Thread().start()
? Hanya penampung yang harus membuat utas. Kami sekarang akan mengembangkan ide ini lebih jauh.
Semua objek juga harus dibuat hanya oleh container . Tentu saja, kami tidak berbicara tentang semua objek, melainkan tentang apa yang disebut objek bisnis. Mereka juga sering disebut sebagai tempat sampah. Kaki dari pendekatan ini tumbuh dari prinsip kelima SOLID, yang mengharuskan penghapusan kelas dan pindah ke antarmuka:
- Modul tingkat atas tidak boleh bergantung pada modul tingkat bawah. Keduanya, dan yang lainnya harus bergantung pada abstraksi.
- Abstraksi seharusnya tidak bergantung pada detail. Implementasinya harus bergantung pada abstraksi.
Modul tidak boleh berisi referensi ke implementasi tertentu, dan semua ketergantungan dan interaksi di antara mereka harus dibangun hanya berdasarkan abstraksi (yaitu, antarmuka). Inti dari aturan ini dapat ditulis dalam satu kalimat: semua dependensi harus dalam bentuk interfaces .
Terlepas dari sifat fundamental dan kesederhanaannya yang tampak, aturan ini paling sering dilanggar. Yakni, setiap kali kita menggunakan operator baru dalam kode program/modul dan membuat objek baru dari tipe tertentu, jadi, alih-alih bergantung pada antarmuka, ketergantungan pada implementasi terbentuk.
Jelas bahwa ini tidak dapat dihindari dan objek harus dibuat di suatu tempat. Namun, paling tidak, Anda perlu meminimalkan jumlah tempat di mana hal ini dilakukan dan di mana kelas ditentukan secara eksplisit, serta melokalkan dan mengisolasi tempat tersebut agar tidak tersebar di seluruh kode program.
Solusi yang sangat bagus adalah ide gila untuk memusatkan pembuatan objek baru di dalam objek dan modul khusus - pabrik, pencari layanan, wadah IoC.
Dalam arti tertentu, keputusan seperti itu mengikuti Prinsip Pilihan Tunggal, yang mengatakan: "Setiap kali sistem perangkat lunak harus mendukung banyak alternatif, daftar lengkapnya harus diketahui hanya oleh satu modul sistem" .
Oleh karena itu, jika di masa mendatang perlu menambahkan opsi baru (atau implementasi baru, seperti dalam kasus membuat objek baru yang sedang kami pertimbangkan), maka cukup memperbarui hanya modul yang berisi informasi ini, dan semua modul lainnya akan tetap tidak terpengaruh dan akan dapat melanjutkan pekerjaannya seperti biasa.
Contoh 1
new ArrayList
Alih-alih menulis sesuatu seperti , akan masuk akal List.new()
jika JDK memberi Anda implementasi daun yang benar: ArrayList, LinkedList, atau bahkan ConcurrentList.
Misalnya, kompiler melihat bahwa ada panggilan ke objek dari utas yang berbeda dan menempatkan implementasi aman utas di sana. Atau terlalu banyak insert di tengah sheet, maka implementasinya akan berbasis LinkedList.
Contoh 2
Ini sudah terjadi dengan macam-macam, misalnya. Kapan terakhir kali Anda menulis algoritme pengurutan untuk mengurutkan koleksi? Sebagai gantinya, sekarang semua orang menggunakan metode Collections.sort()
, dan elemen koleksi harus mendukung antarmuka Sebanding (sebanding).
Jika sort()
Anda meneruskan koleksi kurang dari 10 elemen ke metode, sangat mungkin untuk mengurutkannya dengan jenis gelembung (Bubble sort), dan bukan Quicksort.
Contoh 3
Kompiler sudah memperhatikan bagaimana Anda menggabungkan string dan akan mengganti kode Anda dengan StringBuilder.append()
.
9.2 Pembalikan ketergantungan dalam praktek
Sekarang yang paling menarik: mari pikirkan bagaimana kita bisa menggabungkan teori dan praktik. Bagaimana modul membuat dan menerima "ketergantungan" mereka dengan benar dan tidak melanggar Pembalikan Ketergantungan?
Untuk melakukan ini, saat mendesain modul, Anda harus memutuskan sendiri:
- apa yang dilakukan modul, fungsi apa yang dilakukannya;
- maka modul membutuhkan dari lingkungannya, yaitu objek / modul apa yang harus ditanganinya;
- Dan bagaimana dia akan mendapatkannya?
Untuk mematuhi prinsip-prinsip Pembalikan Ketergantungan, Anda pasti perlu memutuskan objek eksternal mana yang digunakan modul Anda dan bagaimana ia akan mendapatkan referensi ke sana.
Dan inilah opsi berikut:
- modul itu sendiri membuat objek;
- modul mengambil objek dari wadah;
- modul tidak tahu dari mana objek berasal.
Masalahnya adalah untuk membuat objek, Anda perlu memanggil konstruktor dari tipe tertentu, dan akibatnya, modul tidak akan bergantung pada antarmuka, tetapi pada implementasi spesifik. Tetapi jika kita tidak ingin objek dibuat secara eksplisit dalam kode modul, maka kita dapat menggunakan pola Metode Pabrik .
"Intinya adalah bahwa alih-alih secara langsung membuat instance objek melalui yang baru, kami menyediakan beberapa antarmuka untuk kelas klien untuk membuat objek. Karena antarmuka seperti itu selalu dapat diganti dengan desain yang tepat, kami mendapatkan beberapa fleksibilitas saat menggunakan modul tingkat rendah dalam modul tingkat tinggi" .
Dalam kasus di mana diperlukan untuk membuat grup atau keluarga objek terkait, pabrik Abstrak digunakan sebagai pengganti Metode Pabrik .
9.3 Menggunakan Pencari Layanan
Modul mengambil objek yang diperlukan dari orang yang sudah memilikinya. Diasumsikan bahwa sistem memiliki beberapa repositori objek, di mana modul dapat "meletakkan" objeknya dan "mengambil" objek dari repositori.
Pendekatan ini diimplementasikan oleh pola Service Locator , ide utamanya adalah bahwa program memiliki objek yang mengetahui cara mendapatkan semua dependensi (layanan) yang mungkin diperlukan.
Perbedaan utama dari pabrik adalah bahwa Service Locator tidak membuat objek, tetapi sebenarnya sudah berisi objek yang dibuat (atau tahu di mana / bagaimana mendapatkannya, dan jika dibuat, maka hanya sekali pada panggilan pertama). Pabrik di setiap panggilan membuat objek baru yang Anda miliki sepenuhnya dan Anda dapat melakukan apa pun yang Anda inginkan dengannya.
Penting ! Lokasi layanan menghasilkan referensi ke objek yang sama yang sudah ada . Oleh karena itu, Anda harus sangat berhati-hati dengan objek yang dikeluarkan oleh Pencari Layanan, karena orang lain dapat menggunakannya pada saat yang sama dengan Anda.
Objek di Service Locator dapat ditambahkan langsung melalui file konfigurasi, dan memang dengan cara apa pun yang nyaman bagi pemrogram. Service Locator itu sendiri dapat berupa kelas statis dengan seperangkat metode statis, singleton, atau antarmuka, dan dapat diteruskan ke kelas yang diperlukan melalui konstruktor atau metode.
Service Locator terkadang disebut anti-pattern dan tidak disarankan (karena menciptakan koneksi implisit dan hanya memberikan tampilan desain yang bagus). Anda dapat membaca lebih lanjut dari Mark Seaman:
9.4 Injeksi Ketergantungan
Modul sama sekali tidak peduli dengan dependensi "menambang". Itu hanya menentukan apa yang dibutuhkan untuk bekerja, dan semua dependensi yang diperlukan disediakan (diperkenalkan) dari luar oleh orang lain.
Inilah yang disebut - Injeksi Ketergantungan . Biasanya, dependensi yang diperlukan diteruskan sebagai parameter konstruktor (Injeksi Konstruktor) atau melalui metode kelas (Injeksi Setter).
Pendekatan ini membalikkan proses pembuatan dependensi - alih-alih modul itu sendiri, pembuatan dependensi dikendalikan oleh seseorang dari luar. Modul dari pemancar aktif objek menjadi pasif - bukan dia yang menciptakan, tetapi orang lain yang menciptakan untuknya.
Perubahan arah ini disebut Pembalikan Kontrol , atau Prinsip Hollywood - "Jangan hubungi kami, kami akan menghubungi Anda."
Ini adalah solusi yang paling fleksibel, memberikan modul otonomi terbesar . Kami dapat mengatakan bahwa hanya itu yang sepenuhnya menerapkan "Prinsip Tanggung Jawab Tunggal" - modul harus sepenuhnya fokus untuk melakukan tugasnya dengan baik dan tidak mengkhawatirkan hal lain.
Menyediakan modul dengan semua yang diperlukan untuk bekerja adalah tugas terpisah, yang harus ditangani oleh "spesialis" yang sesuai (biasanya wadah tertentu, wadah IOC, bertanggung jawab untuk mengelola dependensi dan penerapannya).
Faktanya, semua yang ada di sini seperti dalam hidup: di perusahaan yang terorganisir dengan baik, program pemrogram, dan meja, komputer, dan semua yang mereka butuhkan untuk bekerja dibeli dan disediakan oleh manajer kantor. Atau, jika Anda menggunakan metafora program sebagai konstruktor, modul tidak boleh memikirkan kabel, orang lain yang terlibat dalam perakitan konstruktor, dan bukan bagiannya sendiri.
Tidaklah berlebihan untuk mengatakan bahwa penggunaan antarmuka untuk menggambarkan ketergantungan antar modul (Inversi Ketergantungan) + pembuatan dan injeksi yang benar dari ketergantungan ini (terutama Injeksi Ketergantungan) adalah teknik utama untuk decoupling .
Mereka berfungsi sebagai fondasi di mana kode longgar digabungkan, fleksibilitasnya, penolakan terhadap perubahan, penggunaan kembali, dan tanpanya semua teknik lain menjadi tidak masuk akal. Ini adalah dasar dari sambungan longgar dan arsitektur yang baik.
Prinsip Pembalikan Kontrol (bersama dengan Dependency Injection dan Service Locator) dibahas secara rinci oleh Martin Fowler. Ada terjemahan dari kedua artikelnya: "Inversion of Control Containers and the Dependency Injection pattern" dan "Inversion of Control" .