Mengapa saya harus menggunakan injeksi ketergantungan?

95

Saya mengalami kesulitan mencari sumber daya tentang mengapa saya harus menggunakan injeksi ketergantungan . Sebagian besar sumber daya yang saya lihat menjelaskan bahwa itu hanya meneruskan sebuah instance objek ke instance objek lain, tetapi mengapa? Apakah ini hanya untuk arsitektur / kode yang lebih bersih atau apakah ini mempengaruhi kinerja secara keseluruhan?

Mengapa saya harus melakukan yang berikut?

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Alih-alih berikut ini?

class Profile {
    public function deactivateProfile()
    {
        $setting = new Setting();
        $setting->isActive = false;
    }
}
Daniel
sumber
8
Anda memperkenalkan dependensi kode keras untuk menonaktifkan Profil () (yang buruk). Anda memiliki lebih banyak kode yang dipisahkan dengan yang pertama, yang membuatnya lebih mudah untuk berubah dan diuji.
Aulis Ronkainen
3
Mengapa Anda melakukan yang pertama? Anda melewati Pengaturan dan kemudian mengabaikan nilainya.
Phil N DeBlanc
57
Saya tidak setuju dengan downvotes. Sementara masalah dapat dianggap sepele bagi para ahli, pertanyaannya pantas: jika invensi ketergantungan harus digunakan, maka harus ada pembenaran untuk menggunakannya.
Flater
13
@ PhilNDeBlanc: Kode ini jelas sangat disederhanakan dan tidak benar-benar menunjukkan logika dunia nyata. Namun, deactivateProfilemenyarankan kepada saya bahwa pengaturan isActiveke false tanpa peduli dengan keadaan sebelumnya adalah pendekatan yang benar di sini. Memanggil metode secara inheren berarti Anda bermaksud mengaturnya sebagai tidak aktif, bukan mendapatkan status aktif saat ini.
Flater
2
Kode Anda bukan contoh injeksi ketergantungan atau inversi. Ini adalah contoh parameterisasi (yang seringkali jauh lebih baik daripada DI).
jpmc26

Jawaban:

104

Keuntungannya adalah bahwa tanpa injeksi ketergantungan, kelas Profil Anda

  • perlu tahu cara membuat objek Pengaturan (melanggar Prinsip Tanggung Jawab Tunggal)
  • Selalu membuat objek Pengaturannya dengan cara yang sama (membuat kopling ketat antara keduanya)

Tetapi dengan injeksi ketergantungan

  • Logika untuk membuat objek Pengaturan ada di tempat lain
  • Sangat mudah untuk menggunakan berbagai jenis objek Pengaturan

Ini mungkin tampak (atau bahkan) tidak relevan dalam kasus khusus ini, tetapi bayangkan jika kita tidak berbicara tentang objek Pengaturan, tetapi objek DataStore, yang mungkin memiliki implementasi yang berbeda, yang menyimpan data dalam file dan yang lain menyimpannya dalam sebuah database. Dan untuk pengujian otomatis Anda ingin implementasi tiruan juga. Sekarang Anda benar-benar tidak ingin kelas Profile ke hardcode mana yang digunakannya - dan bahkan yang lebih penting, Anda benar-benar tidak ingin kelas Profile tahu tentang jalur sistem file, koneksi DB dan kata sandi, jadi pembuatan objek DataStore harus terjadi di tempat lain.

Michael Borgwardt
sumber
23
This may seem (or even be) irrelevant in this particular caseSaya pikir ini sangat relevan. Bagaimana Anda mendapatkan pengaturan? Banyak sistem yang saya lihat akan memiliki set pengaturan default hard-coded dan konfigurasi publik, jadi Anda harus memuat keduanya dan menimpa beberapa nilai dengan pengaturan publik. Anda bahkan mungkin membutuhkan banyak sumber default. Mungkin Anda bahkan mendapatkan beberapa dari disk, yang lain dari DB. Jadi, seluruh logika bahkan untuk mendapatkan pengaturan dapat, dan sering kali, non-sepele - jelas bukan sesuatu yang mengkonsumsi kode yang harus atau akan dipedulikan.
VLAZ
Kami juga dapat menyebutkan bahwa inisialisasi objek untuk komponen non-sepele, seperti layanan web, akan membuat sangat $setting = new Setting();tidak efisien. Injeksi dan instantiasi objek terjadi sekali.
vikingsteve
9
Saya pikir menggunakan tiruan untuk pengujian harus lebih ditekankan. Kemungkinannya adalah jika Anda melihat kode saja, itu selalu akan menjadi objek Pengaturan dan tidak akan pernah berubah, jadi meneruskannya sepertinya merupakan upaya yang sia-sia. Namun, saat pertama kali Anda mencoba menguji objek Profil dengan sendirinya tanpa juga memerlukan objek Pengaturan (menggunakan objek tiruan sebagai solusi) kebutuhannya sangat jelas.
JPhi1618
2
@ JPhi1618 Saya pikir masalah dengan menekankan "DI adalah untuk Pengujian Unit" adalah bahwa itu hanya mengarah pada pertanyaan "mengapa saya perlu Tes Unit". Jawabannya mungkin tampak jelas bagi Anda, dan manfaatnya pasti ada di sana, tetapi bagi seseorang yang baru memulai, dengan mengatakan "Anda perlu melakukan hal yang terdengar rumit ini untuk melakukan hal yang terdengar rumit lainnya" cenderung sedikit turn-off. Jadi ada baiknya menyebutkan kelebihan yang berbeda yang mungkin lebih berlaku untuk apa yang mereka lakukan saat ini.
IMSoP
1
@ user986730 - itu adalah poin yang sangat bagus, dan menyoroti bahwa prinsip sebenarnya di sini adalah Dependency Inversion , di mana Injeksi hanya satu teknik. Jadi misalnya, di C, Anda tidak bisa benar-benar menyuntikkan dependensi menggunakan perpustakaan IOC, tetapi Anda bisa membalikkan mereka pada waktu kompilasi, dengan memasukkan file sumber yang berbeda untuk mengejek Anda, dll, yang memiliki efek yang sama.
Stephen Byrne
64

Ketergantungan Injeksi membuat kode Anda lebih mudah untuk diuji.

Saya mempelajari ini secara langsung ketika saya ditugaskan untuk memperbaiki bug yang sulit ditangkap dalam integrasi PayPal Magento.

Suatu masalah akan muncul ketika PayPal memberi tahu Magento tentang pembayaran yang gagal: Magento tidak akan mendaftarkan kegagalan dengan benar.

Menguji potensi perbaikan "secara manual" akan sangat melelahkan: Anda harus entah bagaimana memicu pemberitahuan PayPal "Gagal". Anda harus mengirim cek-e, membatalkannya, dan menunggu sampai kesalahan keluar. Itu berarti 3+ hari untuk menguji perubahan kode satu karakter!

Untungnya, tampaknya para pengembang inti Magento yang mengembangkan fungsi ini memiliki pengujian dalam pikiran, dan menggunakan pola injeksi ketergantungan untuk membuatnya sepele. Ini memungkinkan kami memverifikasi pekerjaan kami dengan test case sederhana seperti ini:

<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
    function read() {
        // Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
        return "HTTP/1.1 200 OK\n\nVERIFIED";
    }
}

// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
  'invoice'        => '100058137',         // Order ID to test against
  'txn_id'         => '04S87540L2309371A', // Test PayPal transaction ID
  'payment_status' => 'Failed'             // New payment status that Magento should ingest
);

// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());

Saya yakin pola DI memiliki banyak keunggulan lain, tetapi peningkatan testabilitas adalah satu-satunya manfaat terbesar dalam pikiran saya .

Jika Anda ingin tahu tentang solusi untuk masalah ini, lihat repo GitHub di sini: https://github.com/bubbleupdev/BUCorefix_Paypalstatus

Eric Seastrand
sumber
4
Ketergantungan injeksi membuat kode lebih mudah untuk diuji daripada kode dengan dependensi hardcoded. Menghilangkan ketergantungan dari logika bisnis sama sekali lebih baik.
Ant P
1
Dan salah satu cara utama untuk melakukan apa yang disarankan @AntP adalah melalui parameterisasi . Logika untuk mengubah hasil yang kembali dari database menjadi objek yang digunakan untuk mengisi templat halaman (umumnya dikenal sebagai "model") bahkan tidak seharusnya tahu pengambilan sedang terjadi; hanya perlu mendapatkan objek-objek itu sebagai input.
jpmc26
4
@ jpmc26 memang - saya cenderung berbicara tentang inti fungsional, shell imperatif , yang benar-benar hanya nama mewah untuk melewatkan data ke domain Anda alih-alih menyuntikkan dependensi ke dalamnya. Domain ini murni, dapat diuji unit tanpa mengejek dan kemudian hanya dibungkus dengan shell yang mengadaptasi hal-hal seperti kegigihan, pengiriman pesan, dll.
Ant P
1
Saya pikir satu-satunya fokus pada testability berbahaya bagi penerapan DI. Itu membuatnya tidak menarik bagi orang-orang yang merasa seperti mereka tidak perlu banyak pengujian, atau berpikir mereka sudah memiliki pengujian di bawah kendali. Saya berpendapat bahwa hampir tidak mungkin untuk menulis kode yang bersih dan dapat digunakan kembali tanpa DI. Pengujian sangat jauh di daftar manfaat dan mengecewakan untuk melihat jawaban ini peringkat 1 atau 2 dalam setiap pertanyaan tentang manfaat DI.
Carl Leth
26

Mengapa (apa masalahnya)?

Mengapa saya harus menggunakan injeksi ketergantungan?

Mnemonik terbaik yang saya temukan untuk ini adalah " baru adalah lem ": Setiap kali Anda menggunakan newkode Anda, kode itu terikat ke implementasi spesifik itu . Jika Anda berulang kali menggunakan konstruktor baru, Anda akan membuat rantai implementasi spesifik . Dan karena Anda tidak dapat "memiliki" instance kelas tanpa membangunnya, Anda tidak dapat memisahkan rantai itu.

Sebagai contoh, bayangkan Anda sedang menulis video game mobil balap. Anda mulai dengan kelas Game, yang menciptakan RaceTrack, yang menciptakan 8 Cars, yang masing-masing membuat Motor. Sekarang jika Anda ingin 4 tambahan Carsdengan akselerasi yang berbeda, Anda harus mengubah setiap kelas yang disebutkan , kecuali mungkin Game.

Kode pembersih

Apakah ini hanya untuk arsitektur / kode yang lebih bersih

Ya .

Namun, mungkin terlihat kurang jelas dalam situasi ini, karena ini lebih merupakan contoh bagaimana melakukannya . Keuntungan sebenarnya hanya menunjukkan ketika beberapa kelas terlibat dan lebih sulit untuk diperagakan, tetapi bayangkan Anda akan menggunakan DI pada contoh sebelumnya. Kode yang menciptakan semua itu mungkin terlihat seperti ini:

List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
    float acceleration = 0.3f;
    float maxSpeed = 200.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);

Menambahkan 4 mobil yang berbeda sekarang dapat dilakukan dengan menambahkan baris ini:

for(int i=0; i<4; i++){
    float acceleration = 0.5f;
    float maxSpeed = 100.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
  • Tidak ada perubahan RaceTrack, Game, Car, atau Motoryang diperlukan - yang berarti kita bisa 100% yakin bahwa kita tidak memperkenalkan bug baru ada!
  • Alih-alih harus beralih di antara beberapa file, Anda akan dapat melihat perubahan lengkap pada layar Anda secara bersamaan. Ini adalah hasil dari melihat pembuatan / pengaturan / konfigurasi sebagai tanggung jawab sendiri - bukan tugas mobil untuk membangun motor.

Pertimbangan kinerja

atau apakah ini mempengaruhi kinerja secara keseluruhan?

Tidak ada . Tapi jujur ​​sepenuhnya dengan Anda, mungkin saja.

Namun, bahkan dalam kasus itu, jumlah yang sangat kecil sehingga Anda tidak perlu peduli. Jika suatu saat nanti, Anda harus menulis kode untuk tamagotchi dengan setara dengan 5Mhz CPU dan 2MB RAM, maka mungkin Anda mungkin harus peduli tentang ini.

Dalam 99,999% * kasus, ini akan memiliki kinerja yang lebih baik, karena Anda menghabiskan lebih sedikit waktu untuk memperbaiki bug dan lebih banyak waktu meningkatkan algoritma sumber daya berat Anda.

* Nomor benar-benar dibuat

Info tambahan: "hard-coded"

Jangan salah, ini adalah masih sangat banyak "hard-kode" - angka yang ditulis langsung dalam kode. Bukan hard-coded berarti menyimpan nilai-nilai itu dalam file teks - misalnya dalam format JSON - dan kemudian membacanya dari file itu.

Untuk melakukan itu, Anda harus menambahkan kode untuk membaca file dan kemudian mem-parsing JSON. Jika Anda mempertimbangkan contohnya lagi; dalam versi non-DI, a Caratau yang Motorsekarang harus membaca file. Kedengarannya tidak masuk akal.

Dalam versi DI, Anda akan menambahkannya ke kode pengaturan permainan.

R. Schmitz
sumber
3
Ad hard-coded, sebenarnya tidak ada banyak perbedaan antara kode dan file konfigurasi. File yang dibundel dengan aplikasi adalah sumber bahkan jika Anda membaca secara dinamis. Menarik nilai dari kode ke file data di json atau format 'config' apa pun tidak membantu apa pun kecuali nilai-nilai tersebut harus ditimpa oleh pengguna atau bergantung pada lingkungan.
Jan Hudec
3
Saya benar-benar membuat Tamagotchi sekali dengan Arduino (16MHz, 2KB)
Jungkook
@ JanHudec Benar. Saya sebenarnya memiliki penjelasan yang lebih lama di sana, tetapi memutuskan untuk menghapusnya agar lebih pendek dan fokus pada bagaimana hubungannya dengan DI. Ada banyak hal yang tidak 100% benar; secara keseluruhan jawabannya lebih dioptimalkan untuk mendorong "titik" DI tanpa terlalu lama. Atau dengan kata lain, inilah yang ingin saya dengar ketika saya mulai dengan DI.
R. Schmitz
17

Saya selalu bingung dengan injeksi ketergantungan. Tampaknya hanya ada di lingkungan Jawa, tetapi bola itu berbicara dengan sangat hormat. Itu adalah salah satu Pola besar, Anda tahu, yang dikatakan menertibkan kekacauan. Tetapi contoh-contohnya selalu berbelit-belit dan tiruan, membuat non-masalah dan kemudian berangkat untuk menyelesaikannya dengan membuat kode lebih rumit.

Itu lebih masuk akal ketika sesama Python dev memberi saya kebijaksanaan ini: itu hanya melewati argumen ke fungsi. Sama sekali bukan pola; lebih seperti pengingat bahwa Anda dapat meminta sesuatu sebagai argumen, bahkan jika Anda bisa saja memberikan nilai yang masuk akal sendiri.

Jadi pertanyaan Anda kira-kira setara dengan "mengapa fungsi saya harus mengambil argumen?" dan memiliki banyak jawaban yang sama, yaitu: membiarkan penelepon membuat keputusan .

Ini tentu saja memerlukan biaya, karena sekarang Anda memaksa pemanggil untuk membuat beberapa keputusan (kecuali jika Anda membuat argumen opsional), dan antarmuka agak rumit. Sebagai gantinya, Anda mendapatkan fleksibilitas.

Jadi: Apakah ada alasan bagus mengapa Anda perlu menggunakan Settingjenis / nilai khusus ini? Apakah ada alasan yang baik memanggil kode mungkin menginginkan Settingjenis / nilai yang berbeda ? (Ingat, tes adalah kode !)

Eevee
sumber
2
Ya. IoC akhirnya mengklik saya ketika saya menyadari bahwa ini hanyalah tentang "membiarkan penelepon membuat keputusan". IoC itu berarti kontrol komponen dialihkan dari pembuat komponen ke pengguna komponen. Dan karena pada saat itu saya sudah memiliki cukup banyak keluhan dengan perangkat lunak yang menganggap dirinya lebih pintar daripada saya, saya langsung dijual dengan pendekatan DI.
Joker_vD
1
"Ini hanya melewati argumen ke fungsi. Ini hampir tidak ada pola sama sekali" Ini adalah satu-satunya penjelasan yang baik atau bahkan dapat dipahami dari DI yang pernah saya dengar (well kebanyakan orang bahkan tidak berbicara tentang apa itu , melainkan manfaatnya). Dan kemudian mereka menggunakan jargon yang rumit seperti "inversi kontrol" dan "kopling longgar" yang hanya membutuhkan lebih banyak pertanyaan untuk dipahami ketika itu adalah ide yang sangat sederhana.
addison
7

Contoh yang Anda berikan bukanlah injeksi ketergantungan dalam arti klasik. Ketergantungan injeksi biasanya mengacu pada objek yang lewat dalam konstruktor atau dengan menggunakan "setter injection" tepat setelah objek dibuat, untuk menetapkan nilai pada bidang dalam objek yang baru dibuat.

Contoh Anda meneruskan objek sebagai argumen ke metode instance. Metode instance ini kemudian memodifikasi bidang pada objek itu. Ketergantungan injeksi? Tidak. Memecah enkapsulasi dan menyembunyikan data? Benar!

Sekarang, jika kodenya seperti ini:

class Profile {
    private $settings;

    public function __construct(Settings $settings) {
        $this->settings = $settings;
    }

    public function deactive() {
        $this->settings->isActive = false;
    }
}

Maka saya akan mengatakan Anda menggunakan injeksi ketergantungan. Perbedaan penting adalah Settingsobjek yang diteruskan ke konstruktor atau Profileobjek.

Ini berguna jika objek Pengaturan mahal atau kompleks untuk dibangun, atau Pengaturan adalah antarmuka atau kelas abstrak di mana ada beberapa implementasi konkret untuk mengubah perilaku run time.

Karena Anda secara langsung mengakses bidang pada objek Pengaturan daripada memanggil metode, Anda tidak dapat memanfaatkan Polimorfisme, yang merupakan salah satu manfaat injeksi ketergantungan.

Sepertinya Pengaturan untuk Profil khusus untuk profil itu. Dalam hal ini saya akan melakukan salah satu dari yang berikut:

  1. Instantiate objek Pengaturan di dalam konstruktor Profil

  2. Lewati objek Pengaturan di konstruktor dan salin bidang individual yang berlaku untuk Profil

Jujur, dengan meneruskan objek Pengaturan ke deactivateProfiledan kemudian memodifikasi bidang internal objek Pengaturan adalah bau kode. Objek Pengaturan harus menjadi satu-satunya yang memodifikasi bidang internal.

Greg Burghardt
sumber
5
The example you give is not dependency injection in the classical sense.- Tidak masalah. OO orang memiliki benda di otak, tetapi Anda masih menyerahkan ketergantungan pada sesuatu.
Robert Harvey
1
Ketika Anda berbicara tentang " dalam pengertian klasik ", Anda, seperti yang dikatakan @RobertHarvey, berbicara murni dalam istilah OO. Dalam pemrograman fungsional misalnya, menyuntikkan satu fungsi ke fungsi lain (fungsi orde tinggi) adalah contoh klasik paradigma injeksi ketergantungan.
David Arno
3
@RobertHarvey: Saya kira saya mengambil terlalu banyak kebebasan dengan "injeksi ketergantungan." Tempat orang paling sering memikirkan istilah dan menggunakan istilah ini mengacu pada bidang pada objek yang "disuntikkan" pada saat konstruksi objek, atau segera setelah dalam kasus injeksi setter.
Greg Burghardt
@ DavidArno: Ya, Anda benar. OP tampaknya memiliki potongan kode PHP berorientasi objek dalam pertanyaan, jadi saya menjawab dalam hal itu saja, dan tidak membahas pemrograman fungsional --- walaupun dengan PHP Anda bisa menanyakan pertanyaan yang sama dari sudut pandang pemrograman fungsional.
Greg Burghardt
7

Saya tahu saya datang terlambat ke pesta ini tapi saya merasa ada poin penting yang terlewatkan.

Mengapa saya harus melakukan ini:

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Anda seharusnya tidak. Tetapi bukan karena Ketergantungan Injeksi adalah ide yang buruk. Itu karena ini salah.

Mari kita lihat hal-hal ini menggunakan kode. Kami akan melakukan ini:

$profile = new Profile();
$profile->deactivateProfile($setting);

ketika kita mendapatkan hal yang sama dari ini:

$setting->isActive = false; // Deactivate profile

Jadi tentu saja sepertinya buang-buang waktu saja. Itu adalah ketika Anda melakukannya dengan cara ini. Ini bukan penggunaan Dependency Injection terbaik. Ini bahkan bukan penggunaan terbaik dari sebuah kelas.

Sekarang bagaimana jika sebaliknya kita punya ini:

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

Dan sekarang applicationbebas untuk mengaktifkan dan menonaktifkan profiletanpa harus tahu apa-apa khususnya tentang settingitu benar-benar berubah. Kenapa itu bagus? Jika Anda perlu mengubah pengaturan. The applicationberdinding off dari perubahan tersebut sehingga Anda bebas untuk pergi kacang dalam ruang yang terkandung aman tanpa harus melihat segala sesuatu memecah segera setelah Anda menyentuh sesuatu.

Ini mengikuti konstruksi terpisah dari prinsip perilaku . Pola DI di sini sederhana. Bangun semua yang Anda butuhkan pada level serendah yang Anda bisa, sambungkan semuanya, lalu mulailah semua perilaku berdetak dengan satu panggilan.

Hasilnya adalah Anda memiliki tempat terpisah untuk memutuskan apa yang terhubung dengan apa dan tempat berbeda untuk mengelola apa yang mengatakan apa untuk apa pun.

Cobalah itu pada sesuatu yang harus Anda pertahankan dari waktu ke waktu dan lihat apakah itu tidak membantu.

candied_orange
sumber
6

Sebagai pelanggan, ketika Anda menyewa seorang mekanik untuk melakukan sesuatu pada mobil Anda, apakah Anda mengharapkan mekanik tersebut untuk membuat mobil dari awal hanya untuk kemudian bekerja dengannya? Tidak, Anda memberi mekanik mobil yang Anda inginkan untuk mereka kerjakan .

Sebagai pemilik garasi, ketika Anda memerintahkan mekanik untuk melakukan sesuatu pada mobil, apakah Anda mengharapkan mekanik untuk membuat sendiri obeng / kunci pas / suku cadang mobil? Tidak, Anda memberi mekanik bagian / alat yang ia butuhkan

Kenapa kita melakukan ini? Nah, pikirkan tentang itu. Anda adalah pemilik garasi yang ingin mempekerjakan seseorang untuk menjadi mekanik Anda. Anda akan mengajari mereka menjadi mekanik (= Anda akan menulis kode).

Apa yang akan lebih mudah:

  • Ajari mekanik cara memasang spoiler ke mobil menggunakan obeng.
  • Ajari mekanik untuk membuat mobil, buat spoiler, buat obeng, lalu pasang spoiler yang baru dibuat ke mobil yang baru dibuat dengan obeng yang baru dibuat.

Ada manfaat besar jika mekanik Anda tidak membuat semuanya dari awal:

  • Jelas, pelatihan (= pengembangan) secara dramatis dipersingkat jika Anda hanya memasok mekanik Anda dengan alat dan bagian yang ada.
  • Jika mekanik yang sama harus melakukan pekerjaan yang sama beberapa kali, Anda dapat memastikan dia menggunakan kembali obeng alih-alih selalu membuang yang lama dan membuat yang baru.
  • Selain itu, seorang mekanik yang telah belajar untuk menciptakan segala sesuatu harus jauh lebih ahli, dan dengan demikian akan mengharapkan upah yang lebih tinggi. Analogi pengkodean di sini adalah bahwa kelas dengan banyak tanggung jawab jauh lebih sulit untuk dipertahankan daripada kelas dengan satu tanggung jawab yang didefinisikan secara ketat.
  • Selain itu, ketika penemuan baru menghantam pasar dan spoiler sekarang dibuat dari karbon, bukan plastik; Anda harus melatih ulang (= membangun kembali) mekanik ahli Anda. Tetapi mekanik "sederhana" Anda tidak perlu dilatih ulang selama spoiler masih dapat dipasang dengan cara yang sama.
  • Memiliki mekanik yang tidak bergantung pada mobil yang mereka buat sendiri berarti Anda memiliki mekanik yang mampu menangani mobil apa pun yang mungkin mereka terima. Termasuk mobil yang bahkan belum ada pada saat melatih mekanik. Namun, mekanik ahli Anda tidak akan dapat membuat mobil baru yang telah dibuat setelah pelatihan mereka.

Jika Anda merekrut dan melatih mekanik ahli, Anda akan berakhir dengan seorang karyawan yang membutuhkan biaya lebih banyak, membutuhkan lebih banyak waktu untuk melakukan apa yang seharusnya menjadi pekerjaan sederhana, dan akan perlu dilatih ulang setiap kali salah satu dari banyak tanggung jawab mereka membutuhkan untuk diperbarui.

Analogi pengembangannya adalah bahwa jika Anda menggunakan kelas dengan dependensi hardcod, maka Anda akan berakhir dengan kelas yang sulit dipertahankan yang akan membutuhkan pengembangan / perubahan terus menerus setiap kali versi objek yang baru ( Settingsdalam kasus Anda) dibuat, dan Anda Harus mengembangkan logika internal agar kelas memiliki kemampuan untuk membuat berbagai jenis Settingsobjek.

Selain itu, siapa pun yang mengkonsumsi kelas Anda sekarang juga harus meminta kelas untuk membuat Settingsobjek yang benar , bukan hanya untuk dapat lulus kelas Settingsobjek apa pun yang ingin dilewatinya. Ini berarti pengembangan tambahan bagi konsumen untuk mencari tahu bagaimana cara meminta kelas untuk membuat alat yang tepat.


Ya, inversi dependensi membutuhkan sedikit usaha untuk menulis alih-alih hardcoding dependensi. Ya, menyebalkan harus mengetik lebih banyak.

Tapi itu argumen yang sama seperti memilih ke nilai literal hardcode karena "mendeklarasikan variabel membutuhkan lebih banyak usaha". Secara teknis benar, tetapi pro lebih besar daripada kontra dengan beberapa urutan besarnya .

Manfaat inversi ketergantungan tidak dialami ketika Anda membuat versi pertama aplikasi. Manfaat inversi ketergantungan dialami ketika Anda perlu mengubah atau memperluas versi awal itu. Dan jangan menipu diri sendiri dengan berpikir bahwa Anda akan melakukannya dengan benar pertama kali dan tidak perlu memperpanjang / mengubah kode. Anda harus mengubah banyak hal.


apakah ini mempengaruhi kinerja secara keseluruhan?

Ini tidak mempengaruhi kinerja runtime aplikasi. Tapi itu secara besar-besaran berdampak pada waktu pengembangan (dan karenanya kinerja) pengembang .

Flater
sumber
2
Jika Anda menghapus komentar Anda, " Sebagai komentar kecil, pertanyaan Anda berfokus pada inversi ketergantungan, bukan injeksi ketergantungan. Injeksi adalah salah satu cara untuk melakukan inversi, tetapi itu bukan satu-satunya cara. ", Ini akan menjadi jawaban yang sangat baik. Inversi ketergantungan dapat dicapai melalui injeksi atau locator / global. Contoh pertanyaan terkait dengan injeksi. Jadi pertanyaannya adalah tentang injeksi ketergantungan (serta inversi ketergantungan).
David Arno
12
Saya pikir seluruh masalah mobil agak suram dan membingungkan
Ewan
4
@Flater, bagian dari masalah adalah tidak ada yang benar-benar setuju tentang perbedaan antara injeksi ketergantungan, inversi ketergantungan dan inversi kontrol. Satu hal yang pasti, sebuah "wadah" pastinya tidak diperlukan untuk menyuntikkan dependensi ke dalam metode atau konstruktor. DI murni (atau orang miskin) secara spesifik menjelaskan injeksi ketergantungan manual. Ini satu-satunya injeksi ketergantungan yang saya gunakan secara pribadi karena saya tidak menyukai "sihir" yang terkait dengan wadah.
David Arno
1
@Flater Masalah dengan contoh adalah bahwa Anda hampir pasti tidak akan memodelkan masalah dalam kode dengan cara ini. Karena itu tidak wajar, dipaksakan, dan menambahkan lebih banyak pertanyaan daripada jawaban. Itu membuatnya lebih cenderung menyesatkan dan membingungkan daripada menunjukkan.
jpmc26
2
@ gbjbaanb: Tidak ada jawaban saya bahkan jauh menyelidiki polimorfisme. Tidak ada warisan, implementasi antarmuka, atau apa pun yang menyerupai naik / turun jenis. Anda salah membaca jawaban.
Flater
0

Seperti semua pola, sangat valid untuk bertanya "mengapa" untuk menghindari desain kembung.

Untuk injeksi ketergantungan, ini mudah dilihat dengan memikirkan dua, yang paling penting, aspek terpenting dari desain OOP ...

Kopling rendah

Kopling dalam pemrograman komputer :

Dalam rekayasa perangkat lunak, kopling adalah tingkat saling ketergantungan antara modul perangkat lunak; ukuran seberapa dekat terhubung dua rutinitas atau modul; kekuatan hubungan antar modul.

Anda ingin mencapai kopling rendah . Dua hal yang sangat berpasangan berarti bahwa jika Anda mengubah yang satu, kemungkinan besar Anda harus mengubah yang lain. Bug atau batasan dalam satu kemungkinan akan menyebabkan bug / pembatasan di yang lain; dan seterusnya.

Satu kelas objek instantiating yang lain adalah kopling yang sangat kuat, karena yang satu perlu tahu tentang keberadaan yang lain; ia perlu tahu bagaimana membuat instantiate (argumen mana yang dibutuhkan oleh konstruktor), dan argumen itu harus tersedia saat memanggil konstruktor. Juga, tergantung pada apakah bahasa tersebut membutuhkan dekonstruksi eksplisit (C ++), ini akan menimbulkan komplikasi lebih lanjut. Jika Anda memperkenalkan kelas baru (yaitu, a NextSettingsatau apa pun), Anda harus kembali ke kelas asli dan menambahkan lebih banyak panggilan ke konstruktor mereka.

Kohesi tinggi

Kohesi :

Dalam pemrograman komputer, kohesi mengacu pada sejauh mana unsur-unsur di dalam modul menjadi satu.

Ini adalah sisi lain dari koin. Jika Anda melihat satu unit kode (satu metode, satu kelas, satu paket dll), Anda ingin memiliki semua kode di dalam unit itu untuk memiliki tanggung jawab sesedikit mungkin.

Contoh dasar untuk ini adalah pola MVC: Anda memisahkan dengan jelas model domain dari view (GUI) dan lapisan kontrol yang menempelkan keduanya.

Ini menghindari kode mengasapi di mana Anda mendapatkan potongan besar yang melakukan banyak hal berbeda; jika Anda ingin mengubah beberapa bagian dari itu, Anda harus melacak semua fitur lainnya juga, berusaha menghindari bug, dll .; dan Anda dengan cepat memprogram diri Anda ke dalam lubang di mana sangat sulit untuk keluar.

Dengan injeksi dependensi, Anda mendelegasikan pembuatan atau melacak, baik, dependensi ke kelas apa pun (atau file konfigurasi) yang mengimplementasikan DI Anda. Kelas-kelas lain tidak akan terlalu peduli tentang apa yang sebenarnya terjadi - mereka akan bekerja dengan beberapa antarmuka generik, dan tidak tahu apa implementasi yang sebenarnya, yang berarti mereka tidak bertanggung jawab atas hal-hal lain.

AnoE
sumber
-1

Anda harus menggunakan teknik untuk memecahkan masalah yang mereka pecahkan saat Anda memiliki masalah tersebut. Ketergantungan inversi dan injeksi tidak berbeda.

Inversi ketergantungan atau injeksi adalah teknik yang memungkinkan kode Anda untuk memutuskan implementasi metode apa yang dipanggil saat dijalankan. Ini memaksimalkan manfaat dari ikatan yang terlambat. Teknik ini diperlukan ketika bahasa tidak mendukung penggantian fungsi non-instance waktu berjalan. Misalnya, Java tidak memiliki mekanisme untuk mengganti panggilan ke metode statis dengan panggilan ke implementasi yang berbeda; kontras dengan Python, di mana semua yang diperlukan untuk mengganti pemanggilan fungsi adalah untuk mengikat nama ke fungsi yang berbeda (menugaskan kembali variabel yang memegang fungsi).

Mengapa kita ingin memvariasikan implementasi fungsi? Ada dua alasan utama:

  • Kami ingin menggunakan palsu untuk tujuan pengujian. Ini memungkinkan kita untuk menguji kelas yang bergantung pada pengambilan basis data tanpa benar-benar terhubung ke database.
  • Kita perlu mendukung banyak implementasi. Sebagai contoh, kita mungkin perlu mengatur sistem yang mendukung database MySQL dan PostgreSQL.

Anda mungkin juga ingin mencatat inversi wadah kontrol. Ini adalah teknik yang dimaksudkan untuk membantu Anda menghindari pohon konstruksi besar yang kusut yang terlihat seperti kodesemu ini:

thing5 =  new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());

myApp = new MyApp(
    new MyAppDependency1(thing5, thing3),
    new MyAppDependency2(
        new Thing1(),
        new Thing2(new Thing3(thing5, new Thing4(thing5)))
    ),
    ...
    new MyAppDependency15(thing5)
);

Ini memungkinkan Anda mendaftarkan kelas Anda dan kemudian melakukan konstruksi untuk Anda:

injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);

myApp = injector.create(MyApp); // The injector fills in all the construction parameters.

Perhatikan bahwa ini paling sederhana jika kelas yang didaftarkan dapat menjadi lajang tanpa kewarganegaraan .

Kata hati-hati

Perhatikan bahwa inversi dependensi seharusnya bukan jawaban masuk Anda untuk logika decoupling. Cari peluang untuk menggunakan parameterisasi sebagai gantinya. Pertimbangkan metode pseudocode ini misalnya:

myAverageAboveMin()
{
    dbConn = new DbConnection("my connection string");
    dbQuery = dbConn.makeQuery();
    dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
    dbQuery.setParam("min", 5);
    dbQuery.Execute();
    myData = dbQuery.getAll();
    count = 0;
    total = 0;
    foreach (row in myData)
    {
        count++;
        total += row.x;
    }

    return total / count;
}

Kami dapat menggunakan inversi ketergantungan untuk beberapa bagian dari metode ini:

class MyQuerier
{
    private _dbConn;

    MyQueries(dbConn) { this._dbConn = dbConn; }

    fetchAboveMin(min)
    {
        dbQuery = this._dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    private _querier;

    Averager(querier) { this._querier = querier; }

    myAverageAboveMin(min)
    {
        myData = this._querier.fetchAboveMin(min);
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

Tetapi kita seharusnya tidak, setidaknya tidak sepenuhnya. Perhatikan bahwa kami telah membuat kelas stateful dengan Querier. Sekarang memegang referensi ke beberapa objek koneksi dasarnya global. Ini menciptakan masalah seperti kesulitan dalam memahami keadaan keseluruhan program dan bagaimana kelas yang berbeda berkoordinasi satu sama lain. Perhatikan juga bahwa kita terpaksa memalsukan querier atau koneksi jika kita ingin menguji logika rata-rata. Lebih lanjut Pendekatan yang lebih baik adalah meningkatkan parameterisasi :

class MyQuerier
{
    fetchAboveMin(dbConn, min)
    {
        dbQuery = dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    averageData(myData)
    {
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

class StuffDoer
{
    private _querier;
    private _averager;

    StuffDoer(querier, averager)
    {
        this._querier = querier;
        this._averager = averager;
    }

    myAverageAboveMin(dbConn, min)
    {
        myData = this._querier.fetchAboveMin(dbConn, min);
        return this._averager.averageData(myData);
    }
}

Dan koneksi akan dikelola pada tingkat yang lebih tinggi yang bertanggung jawab untuk operasi secara keseluruhan dan tahu apa yang harus dilakukan dengan output ini.

Sekarang kita dapat menguji logika rata-rata sepenuhnya terlepas dari kueri, dan terlebih lagi kita dapat menggunakannya dalam berbagai situasi yang lebih luas. Kita mungkin mempertanyakan apakah kita bahkan membutuhkan MyQuerierdan Averagerobjek, dan mungkin jawabannya adalah kita tidak melakukannya jika kita tidak berniat untuk pengujian unit StuffDoer, dan bukan pengujian unit StuffDoerakan sangat masuk akal karena begitu erat dengan database. Mungkin lebih masuk akal untuk membiarkan tes integrasi menutupinya. Dalam hal ini, kita mungkin bisa membuat fetchAboveMindan averageDatamenjadi metode statis.

jpmc26
sumber
2
" Injeksi ketergantungan adalah teknik yang dimaksudkan untuk membantu Anda menghindari pohon konstruksi yang besar dan kusut ... ". Contoh pertama Anda setelah klaim ini adalah contoh injeksi ketergantungan murni, atau orang miskin. Yang kedua adalah contoh menggunakan wadah IoC untuk "mengotomatisasi" injeksi dependensi tersebut. Keduanya adalah contoh injeksi ketergantungan dalam aksi.
David Arno
@ DavidArno Ya, Anda benar. Saya telah menyesuaikan terminologi.
jpmc26
Ada alasan utama ketiga untuk memvariasikan implementasi, atau setidaknya merancang kode dengan asumsi bahwa implementasi dapat bervariasi: itu mendorong pengembang untuk memiliki kopling longgar dan menghindari penulisan kode yang akan sulit diubah jika implementasi baru diimplementasikan pada suatu waktu di masa depan. Dan sementara itu mungkin tidak menjadi prioritas dalam beberapa proyek (misalnya mengetahui bahwa aplikasi tidak akan pernah ditinjau kembali setelah rilis awal), itu akan menjadi yang lain (misalnya di mana model bisnis perusahaan secara khusus mencoba untuk menawarkan dukungan tambahan / perpanjangan aplikasi mereka ).
Flater
@Flater Dependency injection masih menghasilkan kopling yang kuat. Ini mengikat logika ke antarmuka tertentu dan memerlukan kode yang dimaksud untuk mengetahui tentang apa pun antarmuka itu. Pertimbangkan kode yang mengubah hasil yang diambil dari DB. Jika saya menggunakan DI untuk memisahkan mereka, maka kode masih perlu tahu bahwa pengambilan DB terjadi dan memohonnya. Solusi yang lebih baik adalah jika kode transformasi bahkan tidak tahu pengambilan sedang terjadi . Satu-satunya cara Anda dapat melakukannya adalah jika hasil pengambilan dilewatkan oleh penelepon, daripada menyuntikkan mengambil.
jpmc26
1
@ jpmc26 Tapi dalam (komentar) Anda contoh database bahkan tidak perlu menjadi ketergantungan untuk memulai. Tentu saja menghindari ketergantungan lebih baik untuk kopling longgar, tetapi DI berfokus pada penerapan dependensi yang diperlukan.
Flater