Kapan BUKAN menerapkan Prinsip Ketergantungan Pembalikan?


43

Saat ini saya mencoba mencari tahu SOLID. Jadi Prinsip Ketergantungan Inversi berarti bahwa dua kelas harus berkomunikasi melalui antarmuka, bukan secara langsung. Contoh: Jika class Amemiliki metode, yang mengharapkan pointer ke objek bertipe class B, maka metode ini seharusnya benar-benar mengharapkan objek bertipe abstract base class of B. Ini membantu untuk Buka / Tutup juga.

Asalkan saya mengerti dengan benar, pertanyaan saya adalah apakah itu praktik yang baik untuk menerapkan ini pada semua interaksi kelas atau haruskah saya mencoba berpikir dalam hal lapisan ?

Alasan saya skeptis adalah karena kami membayar sejumlah harga untuk mengikuti prinsip ini. Katakanlah, saya perlu mengimplementasikan fitur Z. Setelah analisis, saya menyimpulkan bahwa fitur Zterdiri dari fungsionalitas A, Bdan C. Saya membuat kelas fasadZ , yang, melalui antarmuka, menggunakan kelas A, Bdan C. Saya mulai mengkode implementasi dan pada titik tertentu saya menyadari bahwa tugas Zsebenarnya terdiri dari fungsionalitas A, Bdan D. Sekarang saya perlu memo Cantarmuka, Cprototipe kelas dan menulis Dantarmuka dan kelas terpisah . Tanpa antarmuka, hanya kelas yang perlu diganti.

Dengan kata lain, untuk mengubah sesuatu, saya perlu mengubah 1. penelepon 2. antarmuka 3. deklarasi 4. implementasinya. Dalam python langsung digabungkan implementasi, saya akan perlu mengubah hanya pelaksanaannya.


13
Pembalikan ketergantungan hanyalah sebuah teknik, jadi itu seharusnya hanya diterapkan ketika dibutuhkan ... tidak ada batas sejauh mana itu dapat diterapkan, jadi jika Anda menerapkannya di mana-mana, Anda akan berakhir dengan sampah: seperti pada setiap situasi lainnya -Teknik khusus.
Frank Hileman

Sederhananya, penerapan beberapa prinsip desain perangkat lunak bergantung pada kemampuan untuk melakukan refactor tanpa ampun ketika persyaratan berubah. Dari mereka, bagian antarmuka diyakini untuk menangkap invarian kontraktual desain terbaik, sedangkan kode sumber (implementasi) diharapkan untuk mentolerir perubahan yang lebih sering.
rwong

@rwong Antarmuka menangkap invarian kontraktual hanya jika Anda menggunakan bahasa yang mendukung invarian kontraktual. Dalam bahasa umum (Java, C #), antarmuka hanyalah satu set tanda tangan API. Menambahkan antarmuka yang berlebihan hanya akan menurunkan desain.
Frank Hileman

Saya akan mengatakan Anda salah mengerti. DIP adalah tentang menghindari ketergantungan waktu kompilasi dari komponen "tingkat tinggi" ke komponen "tingkat rendah", untuk memungkinkan penggunaan kembali komponen tingkat tinggi dalam konteks lain, di mana Anda akan menggunakan implementasi berbeda untuk komponen rendah. -tingkat komponen; ini dilakukan dengan membuat tipe abstrak di level tinggi, yang diimplementasikan oleh komponen level rendah; jadi komponen level tinggi dan level rendah bergantung pada abstraksi ini. Pada akhirnya, komponen tingkat tinggi dan rendah berkomunikasi melalui antarmuka, tetapi ini bukan inti dari DIP.
Rogério

Jawaban:


87

Dalam banyak kartun atau media lain, kekuatan baik dan jahat sering diilustrasikan oleh malaikat dan iblis yang duduk di bahu karakter. Dalam cerita kami di sini, bukannya kebaikan dan kejahatan, kami memiliki SOLID di satu bahu, dan YAGNI (Anda tidak akan membutuhkannya!) Duduk di sisi lain.

Prinsip SOLID yang diambil secara maksimal paling sesuai untuk sistem perusahaan yang besar, kompleks, dan sangat dapat dikonfigurasi. Untuk sistem yang lebih kecil, atau lebih spesifik, tidak tepat untuk membuat semuanya sangat fleksibel, karena waktu yang Anda habiskan untuk mengabstraksi sesuatu tidak akan terbukti bermanfaat.

Melewati antarmuka alih-alih kelas konkret terkadang berarti misalnya Anda dapat dengan mudah bertukar membaca dari file untuk aliran jaringan. Namun, untuk sejumlah besar proyek perangkat lunak, fleksibilitas semacam itu tidak akan pernah dibutuhkan, dan Anda mungkin juga hanya lulus kelas file konkret dan menyebutnya sehari dan luangkan sel-sel otak Anda.

Bagian dari seni pengembangan perangkat lunak adalah memiliki perasaan yang baik tentang apa yang mungkin berubah seiring berjalannya waktu, dan apa yang tidak. Untuk hal-hal yang cenderung berubah, gunakan antarmuka dan konsep SOLID lainnya. Untuk hal-hal yang tidak akan, gunakan YAGNI dan hanya lulus jenis beton, lupakan kelas pabrik, lupakan semua runtime yang terhubung dan konfigurasi, dll, dan lupakan banyak abstraksi SOLID. Dalam pengalaman saya, pendekatan YAGNI terbukti benar jauh lebih sering daripada tidak.


19
Pengantar pertama saya ke SOLID adalah sekitar 15 tahun yang lalu tentang sistem baru yang kami bangun. Kita semua minum orang bantuan kool. Jika ada yang menyebutkan sesuatu yang terdengar seperti YAGNI, kami seperti "Pfffft ... plebeian". Saya mendapat kehormatan (horor?) Menonton sistem yang berkembang selama dekade berikutnya. Itu menjadi kekacauan yang sulit yang tak seorang pun bisa mengerti, bahkan kami para pendiri. Arsitek cinta SOLID. Orang yang benar-benar mendapatkan cinta hidup mereka YAGNI. Tidak ada yang sempurna, tetapi YAGNI lebih dekat ke sempurna, dan harus menjadi standar Anda jika Anda tidak tahu apa yang Anda lakukan. :-)
Calphool

11
@ Nah, Ya, kami melakukan itu pada sebuah proyek. Pergi gila dengan itu. Sekarang tes kami tidak mungkin untuk dibaca atau dipertahankan, sebagian karena ejekan yang berlebihan. Di atas semua itu, karena injeksi ketergantungan, itu adalah rasa sakit di belakang untuk menavigasi kode ketika Anda mencoba untuk mencari sesuatu. SOLID bukan peluru perak. YAGNI bukan peluru perak. Pengujian otomatis bukanlah peluru perak. Tidak ada yang bisa menyelamatkan Anda dari melakukan kerja keras untuk memikirkan apa yang Anda lakukan dan membuat keputusan apakah itu akan membantu atau menghambat pekerjaan Anda atau orang lain.
jpmc26

22
Banyak sentimen anti-SOLID di sini. SOLID dan YAGNI bukan dua ujung spektrum. Mereka seperti koordinat X dan Y pada grafik. Sistem yang baik hanya memiliki sedikit kode berlebihan (YAGNI) DAN mengikuti prinsip-prinsip SOLID.
Stephen

31
Meh, (a) Saya tidak setuju bahwa SOLID = tegas dan (b) inti dari SOLID adalah bahwa kita cenderung menjadi peramal yang sangat buruk tentang apa yang akan dibutuhkan. Saya harus setuju dengan @Stephen di sini. YAGNI mengatakan bahwa kita seharusnya tidak mencoba mengantisipasi persyaratan di masa depan yang tidak dijabarkan dengan jelas hari ini. SOLID mengatakan bahwa kita harus mengharapkan desain untuk berkembang dari waktu ke waktu dan menerapkan teknik sederhana tertentu untuk memfasilitasi itu. Mereka tidak saling eksklusif; keduanya adalah teknik untuk beradaptasi dengan perubahan persyaratan. Masalah sebenarnya terjadi ketika Anda mencoba mendesain untuk persyaratan yang tidak jelas atau sangat jauh.
Aaronaught

8
"Anda dapat dengan mudah bertukar membaca dari file untuk aliran jaringan" - ini adalah contoh yang baik di mana deskripsi DI yang terlalu disederhanakan menyebabkan orang tersesat. Orang kadang berpikir (pada dasarnya), "metode ini akan menggunakan File, jadi alih-alih itu akan membutuhkan IFile, pekerjaan yang dilakukan". Kemudian mereka tidak dapat dengan mudah mengganti aliran jaringan, karena mereka terlalu banyak menuntut antarmuka, dan ada operasi dalam IFilemetode bahkan tidak menggunakan, yang tidak berlaku untuk soket, sehingga soket tidak dapat diimplementasikan IFile. Salah satu hal yang DI bukan peluru perak, adalah menciptakan abstraksi yang tepat (interface) :-)
Steve Jessop

11

Dalam kata-kata awam:

Menerapkan DIP itu mudah dan menyenangkan . Tidak mendapatkan desain yang benar pada upaya pertama tidak cukup alasan menyerah pada DIP sama sekali.

  • Biasanya IDE membantu Anda melakukan refactoring semacam itu, beberapa bahkan memungkinkan Anda untuk mengekstrak antarmuka dari kelas yang sudah diterapkan
  • Hampir tidak mungkin untuk mendapatkan desain yang benar pertama kali
  • Alur kerja normal melibatkan mengubah dan memikirkan kembali antarmuka pada tahap pertama pengembangan
  • Seiring perkembangannya, ia semakin matang dan Anda akan memiliki lebih sedikit alasan untuk memodifikasi antarmuka
  • Pada tahap lanjut antarmuka (desain) akan matang dan hampir tidak akan berubah
  • Sejak saat itu, Anda mulai menuai manfaat, karena aplikasi Anda terbuka untuk ditingkatkan.

Di sisi lain, pemrograman dengan antarmuka dan OOD dapat membawa sukacita kembali ke keahlian pemrograman yang terkadang basi.

Beberapa orang mengatakan ini menambah kompleksitas tetapi saya pikir situs oposite itu benar. Bahkan untuk proyek kecil. Itu membuat pengujian / mengejek lebih mudah. Itu membuat kode Anda memiliki lebih sedikit jika ada casepernyataan atau bersarang ifs. Ini mengurangi kompleksitas siklomatik dan membuat Anda berpikir dengan cara yang segar. Itu membuat pemrograman lebih mirip dengan desain dan manufaktur dunia nyata.


5
Saya tidak tahu bahasa atau IDE apa yang digunakan oleh OP, tetapi dalam VS 2013 sangat sederhana untuk bekerja melawan antarmuka, mengekstrak antarmuka dan mengimplementasikannya, dan penting jika TDD digunakan. Tidak ada overhead pengembangan tambahan untuk mengembangkan menggunakan prinsip-prinsip ini.
stephenbayer

Mengapa jawaban ini berbicara tentang DI jika pertanyaannya adalah tentang DIP? DIP adalah konsep dari tahun 1990-an, sedangkan DI adalah dari tahun 2004. Mereka sangat berbeda.
Rogério

1
(Komentar saya sebelumnya dimaksudkan untuk jawaban lain; abaikan saja.) "Pemrograman ke antarmuka" jauh lebih umum daripada DIP, tetapi ini bukan tentang membuat setiap kelas mengimplementasikan antarmuka yang terpisah. Dan itu hanya membuat "pengujian / cemoohan" lebih mudah jika alat pengujian / cemoohan menderita keterbatasan serius.
Rogério

@ Rogério Biasanya saat menggunakan DI, tidak setiap kelas mengimplementasikan antarmuka yang terpisah. Satu antarmuka sedang diimplementasikan oleh beberapa kelas adalah umum.
Tulains Córdova

@ Rogério saya mengoreksi jawaban saya, setiap kali saya menyebutkan DI, saya bermaksud DIP.
Tulains Córdova

9

Gunakan inversi ketergantungan yang masuk akal.

Satu contoh tandingan ekstrim adalah kelas "string" yang disertakan dalam banyak bahasa. Ini mewakili konsep primitif, pada dasarnya array karakter. Dengan asumsi Anda dapat mengubah kelas inti ini, tidak masuk akal untuk menggunakan DI di sini karena Anda tidak perlu menukar keadaan internal dengan sesuatu yang lain.

Jika Anda memiliki sekelompok objek yang digunakan secara internal dalam modul yang tidak terpapar ke modul lain atau digunakan kembali di mana saja, mungkin tidak ada gunanya upaya untuk menggunakan DI.

Ada dua tempat di mana DI seharusnya digunakan secara otomatis menurut saya:

  1. Dalam modul yang dirancang untuk ekstensi. Jika seluruh tujuan modul adalah untuk memperpanjangnya dan mengubah perilaku, masuk akal untuk memanggang DI sejak awal.

  2. Dalam modul yang Anda refactoring untuk tujuan penggunaan kembali kode. Mungkin Anda memberi kode pada sebuah kelas untuk melakukan sesuatu, kemudian menyadari kemudian bahwa dengan refactor Anda dapat memanfaatkan kode itu di tempat lain dan ada kebutuhan untuk melakukannya . Itu adalah kandidat yang bagus untuk DI dan perubahan ekstensibilitas lainnya.

Kunci-kunci di sini digunakan di tempat yang diperlukan karena akan memperkenalkan kompleksitas tambahan, dan pastikan Anda mengukur kebutuhan itu baik melalui persyaratan teknis (poin satu) atau tinjauan kode kuantitatif (poin dua).

DI adalah alat yang hebat, tetapi sama seperti alat *, itu bisa digunakan secara berlebihan atau disalahgunakan.

* Pengecualian terhadap aturan di atas: gergaji balasan adalah alat yang sempurna untuk pekerjaan apa pun. Jika tidak memperbaiki masalah Anda, itu akan menghapusnya. Secara permanen.


3
Bagaimana jika "masalah Anda" adalah lubang di dinding? Gergaji tidak akan menghapusnya; itu akan memperburuknya. ;)
Mason Wheeler

3
@MasonWheeler dengan gergaji yang kuat dan menyenangkan, "hole in wall" bisa berubah menjadi "doorway" yang merupakan aset yang bermanfaat :-)

1
Tidak bisakah kamu menggunakan gergaji untuk membuat lubang untuk lubang itu?
JeffO

Meskipun ada beberapa keuntungan memiliki jenis non-pengguna-extensible String, ada banyak kasus di mana representasi alternatif akan membantu jika jenis memiliki rangkaian operasi virtual yang baik (misalnya menyalin substring ke bagian tertentu dari a short[], melaporkan apakah substring tidak atau mungkin hanya mengandung ASCII, coba salin substring yang diyakini hanya mengandung ASCII ke bagian tertentu dari byte[], dll.) Kerangka kerja buruknya tidak memiliki tipe string mereka yang mengimplementasikan antarmuka terkait string yang berguna.
supercat

1
Mengapa jawaban ini berbicara tentang DI jika pertanyaannya adalah tentang DIP? DIP adalah konsep dari tahun 1990-an, sedangkan DI adalah dari tahun 2004. Mereka sangat berbeda.
Rogério

5

Tampak bagi saya bahwa pertanyaan awal hilang bagian dari titik DIP.

Alasan saya skeptis adalah karena kami membayar sejumlah harga untuk mengikuti prinsip ini. Katakanlah, saya perlu mengimplementasikan fitur Z. Setelah analisis, saya menyimpulkan bahwa fitur Z terdiri dari fungsi A, B dan C. Saya membuat kelas fasade Z, yang, melalui antarmuka, menggunakan kelas A, B dan C. Saya mulai mengkode implementasi dan pada titik tertentu saya menyadari bahwa tugas Z sebenarnya terdiri dari fungsi A, B dan D. Sekarang saya perlu memo antarmuka C, prototipe kelas C dan menulis antarmuka dan kelas D yang terpisah. Tanpa antarmuka, hanya gelombang yang perlu diganti.

Untuk benar-benar mengambil keuntungan dari DIP, Anda harus membuat kelas Z terlebih dahulu, dan menyebutnya fungsi kelas A, B dan C (yang belum dikembangkan). Ini memberi Anda API untuk kelas A, B dan C. Kemudian Anda pergi dan membuat kelas A, B dan C dan isi detailnya. Anda harus secara efektif membuat abstraksi yang Anda butuhkan saat Anda membuat kelas Z, berdasarkan sepenuhnya pada apa yang dibutuhkan kelas Z. Anda bahkan dapat menulis tes di sekitar kelas Z sebelum kelas A, B atau C bahkan ditulis.

Ingat bahwa DIP mengatakan bahwa "Modul tingkat tinggi seharusnya tidak bergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi."

Setelah Anda mengetahui apa yang dibutuhkan kelas Z dan cara yang diinginkannya untuk mendapatkan apa yang dibutuhkan, Anda dapat mengisi detailnya. Tentu, terkadang perubahan harus dilakukan ke kelas Z, tetapi 99% dari waktu ini tidak akan menjadi masalah.

Tidak akan pernah ada kelas D karena Anda mengetahui bahwa Z membutuhkan A, B dan C sebelum ditulis. Perubahan persyaratan adalah cerita yang berbeda sama sekali.


5

Jawaban singkatnya adalah "hampir tidak pernah", tetapi sebenarnya ada beberapa tempat di mana DIP tidak masuk akal:

  1. Pabrik atau pembangun, yang tugasnya menciptakan objek. Ini pada dasarnya adalah "leaf node" dalam suatu sistem yang sepenuhnya merangkul IoC. Pada titik tertentu, sesuatu harus benar-benar membuat objek Anda, dan tidak dapat bergantung pada hal lain untuk melakukan itu. Dalam banyak bahasa, wadah IoC dapat melakukannya untuk Anda, tetapi kadang-kadang Anda perlu melakukannya dengan cara kuno.

  2. Implementasi struktur data dan algoritma. Secara umum, dalam kasus ini karakteristik penting yang Anda optimalkan (seperti waktu berjalan dan kompleksitas asimptotik) bergantung pada tipe data spesifik yang digunakan. Jika Anda menerapkan tabel hash, Anda benar-benar perlu tahu bahwa Anda bekerja dengan array untuk penyimpanan, bukan daftar yang ditautkan, dan hanya tabel itu sendiri yang tahu cara mengalokasikan array dengan benar. Anda juga tidak ingin meneruskan dalam array yang bisa berubah dan meminta penelepon memecahkan tabel hash Anda dengan mengutak-atik isinya.

  3. Kelas model domain . Ini menerapkan logika bisnis Anda, dan (sebagian besar waktu) hanya masuk akal untuk memiliki satu implementasi, karena (sebagian besar waktu) Anda hanya mengembangkan perangkat lunak untuk satu bisnis. Meskipun beberapa kelas model domain dapat dibangun menggunakan kelas model domain lain, ini umumnya akan didasarkan pada kasus per kasus. Karena objek model domain tidak menyertakan fungsionalitas yang dapat diejek dengan bermanfaat, tidak ada manfaat testability atau rawatan untuk DIP.

  4. Objek apa pun yang disediakan sebagai API eksternal dan perlu membuat objek lain, yang detail implementasinya tidak ingin Anda ungkapkan kepada publik. Ini termasuk dalam kategori umum "desain perpustakaan berbeda dari desain aplikasi". Perpustakaan atau kerangka kerja dapat menggunakan DI secara liberal, tetapi pada akhirnya harus melakukan beberapa pekerjaan yang sebenarnya, kalau tidak itu bukan perpustakaan yang sangat berguna. Katakanlah Anda sedang mengembangkan perpustakaan jaringan; Anda benar-benar tidak ingin konsumen dapat memberikan implementasi soketnya sendiri. Anda mungkin secara internal menggunakan abstraksi soket, tetapi API yang Anda paparkan kepada penelepon akan membuat soketnya sendiri.

  5. Tes unit, dan tes ganda. Palsu dan bertopik seharusnya melakukan satu hal dan melakukannya dengan sederhana. Jika Anda memiliki palsu yang cukup rumit untuk dikhawatirkan apakah perlu atau tidak melakukan injeksi ketergantungan, maka itu mungkin terlalu rumit (mungkin karena itu mengimplementasikan antarmuka yang juga terlalu rumit).

Mungkin ada lebih banyak; ini yang saya lihat agak sering.


Bagaimana "kapan pun Anda bekerja dalam bahasa yang dinamis"?
Kevin

Tidak? Saya melakukan banyak pekerjaan dalam JavaScript dan masih berlaku juga di sana. Namun, "O" dan "I" di SOLID bisa menjadi sedikit buram.
Aaronaught

Huh ... Saya menemukan bahwa jenis kelas Python dikombinasikan dengan mengetik bebek membuatnya kurang perlu.
Kevin

DIP sama sekali tidak ada hubungannya dengan sistem tipe. Dan dengan cara apa "tipe kelas" unik untuk python? Saat Anda ingin menguji sesuatu secara terpisah, Anda harus mengganti tes ganda dengan dependensinya. Double tes tersebut dapat berupa implementasi alternatif dari sebuah antarmuka (dalam bahasa yang diketik secara statis) atau mereka dapat berupa objek anonim atau tipe alternatif yang kebetulan memiliki metode / fungsi yang sama pada mereka (mengetik bebek). Dalam kedua kasus tersebut, Anda masih perlu cara untuk benar-benar menggantikan sebuah instance.
Aaronaught

1
@Kevin python bukanlah bahasa pertama yang memiliki pengetikan dinamis atau longgar. Ini juga sama sekali tidak relevan. Pertanyaannya bukan apa jenis objek itu tetapi bagaimana / di mana objek itu dibuat. Ketika sebuah objek membuat dependensinya sendiri, Anda dipaksa untuk menguji unit apa yang harus menjadi rincian implementasi, dengan melakukan hal-hal mengerikan seperti mematikan konstruktor kelas yang tidak disebutkan oleh API publik. Dan melupakan tentang pengujian, perilaku campuran dan konstruksi objek hanya menyebabkan kopling ketat. Mengetik bebek tidak menyelesaikan salah satu dari masalah itu.
Aaronaught

2

Beberapa tanda Anda mungkin menerapkan DIP pada tingkat yang terlalu mikro, di mana itu tidak memberikan nilai:

  • Anda memiliki pasangan C / CImpl atau IC / C, dengan hanya satu implementasi antarmuka itu
  • tanda tangan di antarmuka dan implementasi Anda cocok satu-satu (melanggar prinsip KERING)
  • Anda sering mengganti C dan CImpl secara bersamaan.
  • C adalah internal untuk proyek Anda dan tidak dibagikan di luar proyek Anda sebagai perpustakaan.
  • Anda frustrasi oleh F3 di Eclipse / F12 di Visual Studio membawa Anda ke antarmuka, bukan kelas yang sebenarnya

Jika ini yang Anda lihat, Anda mungkin lebih baik hanya memiliki panggilan Z secara langsung dan melewati antarmuka.

Juga, saya tidak memikirkan metode dekorasi dengan injeksi dependensi / kerangka kerja proksi dinamis (Spring, Java EE) dengan cara yang sama seperti DIP SOLID yang sebenarnya - ini lebih seperti detail implementasi tentang cara kerja metode dekorasi di tumpukan teknologi itu. Komunitas Java EE menganggapnya sebagai peningkatan yang Anda tidak perlu pasang Foo / FooImpl seperti dulu ( referensi ). Sebaliknya, Python mendukung dekorasi fungsi sebagai fitur bahasa kelas satu.

Lihat juga posting blog ini .


0

Jika Anda selalu membalikkan dependensi Anda, maka semua dependensi Anda terbalik. Yang berarti bahwa jika Anda mulai dengan kode berantakan dengan simpul dependensi, itulah yang masih (benar-benar) miliki, hanya terbalik. Di situlah Anda mendapatkan masalah yang perlu diubah setiap antarmuka untuk mengubah antarmuka.

Titik inversi ketergantungan adalah Anda secara selektif membalikkan dependensi yang membuat berbagai hal menjadi kusut. Yang harus beralih dari A ke B ke C masih tetap melakukannya, itu yang pergi dari C ke A yang sekarang beralih dari A ke C.

Hasilnya harus berupa grafik dependensi yang bebas dari siklus - DAG. Ada berbagai alat yang akan memeriksa properti ini, dan menggambar grafik.

Untuk penjelasan lebih lengkap, lihat artikel ini :

Inti dari penerapan Prinsip Ketergantungan Pembalikan dengan benar adalah ini:

Pisahkan kode / layanan / ... Anda bergantung pada antarmuka dan implementasi. Antarmuka merestrukturisasi ketergantungan dalam jargon kode yang menggunakannya, implementasi mengimplementasikannya dalam hal teknik yang mendasarinya.

Implementasinya tetap di tempatnya. Tetapi antarmuka memiliki fungsi yang berbeda sekarang (dan menggunakan jargon / bahasa yang berbeda), menggambarkan sesuatu yang bisa dilakukan dengan menggunakan kode. Pindahkan ke paket itu. Dengan tidak menempatkan antarmuka dan implementasi dalam paket yang sama, dependensi (arah) terbalik dari pengguna → implementasi ke implementasi → pengguna.

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.