Apa prinsip inversi ketergantungan dan mengapa itu penting?
Apa prinsip inversi ketergantungan dan mengapa itu penting?
Jawaban:
Lihat dokumen ini: Prinsip Pembalikan Ketergantungan .
Pada dasarnya dikatakan:
Singkatnya, mengapa itu penting, perubahan itu berisiko, dan dengan bergantung pada konsep alih-alih pada implementasi, Anda mengurangi kebutuhan akan perubahan di situs panggilan.
Secara efektif, DIP mengurangi kopling antara potongan kode yang berbeda. Idenya adalah bahwa meskipun ada banyak cara untuk mengimplementasikan, katakanlah, fasilitas logging, cara Anda akan menggunakannya harus relatif stabil dalam waktu. Jika Anda dapat mengekstrak antarmuka yang mewakili konsep logging, antarmuka ini akan jauh lebih stabil dalam waktu daripada implementasinya, dan situs panggilan harus jauh lebih sedikit dipengaruhi oleh perubahan yang Anda buat sambil mempertahankan atau memperluas mekanisme logging tersebut.
Dengan juga membuat implementasi bergantung pada sebuah antarmuka, Anda mendapatkan kemungkinan untuk memilih pada saat run-time implementasi mana yang lebih cocok untuk lingkungan khusus Anda. Tergantung pada kasusnya, ini mungkin menarik juga.
Buku Pengembangan Perangkat Lunak Agile, Prinsip, Pola, dan Praktik serta Prinsip, Pola, dan Praktek Agile dalam C # adalah sumber daya terbaik untuk memahami sepenuhnya tujuan dan motivasi asli di balik Prinsip Ketergantungan Inversi. Artikel "Prinsip Ketergantungan Pembalikan" juga merupakan sumber yang bagus, tetapi karena fakta bahwa itu adalah versi ringkas dari draft yang akhirnya masuk ke dalam buku-buku yang disebutkan sebelumnya, itu meninggalkan beberapa diskusi penting tentang konsep suatu kepemilikan paket dan antarmuka yang merupakan kunci untuk membedakan prinsip ini dari saran yang lebih umum untuk "program ke antarmuka, bukan implementasi" ditemukan dalam Pola Desain buku (Gamma, et. al).
Untuk memberikan ringkasan, Prinsip Pembalikan Ketergantungan terutama tentang membalikkan arah ketergantungan konvensional dari komponen "tingkat lebih tinggi" ke komponen "tingkat lebih rendah" sehingga komponen "tingkat bawah" bergantung pada antarmuka yang dimiliki oleh komponen "tingkat lebih tinggi" . (Catatan: komponen "tingkat yang lebih tinggi" di sini mengacu pada komponen yang membutuhkan dependensi / layanan eksternal, belum tentu posisi konseptualnya dalam arsitektur berlapis). Dalam melakukan hal itu, kopling tidak berkurang sebanyak ia bergeser dari komponen yang secara teoritis kurang berharga untuk komponen yang secara teori lebih berharga.
Hal ini dicapai dengan mendesain komponen yang dependensi eksternalnya dinyatakan dalam bentuk antarmuka yang implementasinya harus disediakan oleh konsumen komponen tersebut. Dengan kata lain, antarmuka yang didefinisikan mengekspresikan apa yang dibutuhkan oleh komponen, bukan bagaimana Anda menggunakan komponen (misalnya "INeedSomething", bukan "IDoSomething").
Apa yang tidak dirujuk oleh Prinsip Inversi Ketergantungan adalah praktik sederhana mengabstraksi dependensi melalui penggunaan antarmuka (mis. MyService → [ILogger ⇐ Logger]). Walaupun ini memisahkan komponen dari detail implementasi spesifik dari dependensi, itu tidak membalikkan hubungan antara konsumen dan ketergantungan (mis. [MyService → IMyServiceLogger] ⇐ Logger.
Pentingnya Prinsip Ketergantungan Inversi dapat didistilasi ke tujuan tunggal untuk dapat menggunakan kembali komponen perangkat lunak yang bergantung pada dependensi eksternal untuk sebagian dari fungsinya (logging, validasi, dll.)
Dalam tujuan umum penggunaan kembali ini, kami dapat menggambarkan dua sub-jenis penggunaan kembali:
Menggunakan komponen perangkat lunak dalam banyak aplikasi dengan implementasi sub-dependensi (mis. Anda telah mengembangkan wadah DI dan ingin memberikan pencatatan, tetapi tidak ingin memasangkan wadah Anda ke logger tertentu sehingga setiap orang yang menggunakan wadah Anda juga harus gunakan perpustakaan logging yang Anda pilih).
Menggunakan komponen perangkat lunak dalam konteks yang berkembang (misalnya Anda telah mengembangkan komponen logika bisnis yang tetap sama di beberapa versi aplikasi di mana detail implementasi berkembang).
Dengan kasus pertama menggunakan kembali komponen di beberapa aplikasi, seperti dengan perpustakaan infrastruktur, tujuannya adalah untuk menyediakan kebutuhan infrastruktur inti kepada konsumen Anda tanpa menyatukan konsumen Anda dengan sub-dependensi perpustakaan Anda sendiri karena mengambil dependensi pada dependensi tersebut mengharuskan Anda konsumen membutuhkan ketergantungan yang sama juga. Ini bisa menjadi masalah ketika konsumen perpustakaan Anda memilih untuk menggunakan perpustakaan yang berbeda untuk kebutuhan infrastruktur yang sama (misalnya NLog vs log4net), atau jika mereka memilih untuk menggunakan versi terbaru dari perpustakaan yang diperlukan yang tidak kompatibel dengan versi. diperlukan oleh perpustakaan Anda.
Dengan kasus kedua menggunakan kembali komponen logika bisnis (yaitu "komponen tingkat lebih tinggi"), tujuannya adalah untuk mengisolasi implementasi domain inti dari aplikasi Anda dari perubahan kebutuhan detail implementasi Anda (mis. Mengubah / meningkatkan perpustakaan persistensi, perpustakaan perpesanan) , strategi enkripsi, dll.). Idealnya, mengubah rincian implementasi aplikasi tidak boleh merusak komponen yang merangkum logika bisnis aplikasi.
Catatan: Beberapa orang mungkin keberatan untuk menggambarkan kasus kedua ini sebagai penggunaan kembali yang sebenarnya, dengan alasan bahwa komponen seperti komponen logika bisnis yang digunakan dalam aplikasi yang berkembang hanya mewakili penggunaan tunggal. Idenya di sini, bagaimanapun, adalah bahwa setiap perubahan pada detail implementasi aplikasi membuat konteks baru dan oleh karena itu use case yang berbeda, meskipun tujuan akhir dapat dibedakan sebagai isolasi vs portabilitas.
Meskipun mengikuti Prinsip Ketergantungan Inversi dalam kasus kedua ini dapat menawarkan beberapa manfaat, perlu dicatat bahwa nilainya seperti yang diterapkan pada bahasa modern seperti Java dan C # jauh berkurang, mungkin sampai pada titik yang tidak relevan. Seperti dibahas sebelumnya, DIP melibatkan pemisahan rincian implementasi ke dalam paket yang terpisah sepenuhnya. Akan tetapi, dalam kasus aplikasi yang berkembang, hanya dengan menggunakan antarmuka yang didefinisikan dalam domain bisnis akan mencegah tidak perlu memodifikasi komponen tingkat yang lebih tinggi karena perubahan kebutuhan komponen detail implementasi, bahkan jika detail implementasi akhirnya berada dalam paket yang sama . Bagian dari prinsip ini mencerminkan aspek-aspek yang berkaitan dengan bahasa yang dilihat ketika prinsip tersebut dikodifikasi (yaitu C ++) yang tidak relevan dengan bahasa yang lebih baru. Yang mengatakan,
Diskusi yang lebih panjang tentang prinsip ini terkait dengan penggunaan antarmuka yang sederhana, Dependency Injection, dan pola Interface Terpisah dapat ditemukan di sini . Selain itu, diskusi tentang bagaimana prinsip ini terkait dengan bahasa yang diketik secara dinamis seperti JavaScript dapat ditemukan di sini .
Ketika kita merancang aplikasi perangkat lunak, kita dapat mempertimbangkan kelas tingkat rendah kelas yang mengimplementasikan operasi dasar dan primer (akses disk, protokol jaringan, ...) dan kelas tingkat tinggi kelas yang merangkum logika kompleks (arus bisnis, ...).
Yang terakhir bergantung pada kelas tingkat rendah. Cara alami untuk menerapkan struktur seperti itu adalah dengan menulis kelas tingkat rendah dan begitu kita memilikinya untuk menulis kelas tingkat tinggi yang kompleks. Karena kelas tingkat tinggi didefinisikan dalam hal orang lain, ini tampaknya cara logis untuk melakukannya. Tapi ini bukan desain yang fleksibel. Apa yang terjadi jika kita perlu mengganti kelas tingkat rendah?
Prinsip Ketergantungan Pembalikan menyatakan bahwa:
Prinsip ini berupaya untuk "membalikkan" gagasan konvensional bahwa modul tingkat tinggi dalam perangkat lunak harus bergantung pada modul tingkat bawah. Di sini modul tingkat tinggi memiliki abstraksi (misalnya, memutuskan metode antarmuka) yang diimplementasikan oleh modul tingkat bawah. Dengan demikian membuat modul level bawah tergantung pada modul level yang lebih tinggi.
Ketergantungan inversi diterapkan dengan baik memberikan fleksibilitas dan stabilitas di tingkat seluruh arsitektur aplikasi Anda. Ini akan memungkinkan aplikasi Anda berkembang lebih aman dan stabil.
Secara tradisional UI arsitektur berlapis tergantung pada lapisan bisnis dan ini pada gilirannya tergantung pada lapisan akses data.
Anda harus memahami layer, paket, atau pustaka. Mari kita lihat bagaimana kodenya.
Kami akan memiliki pustaka atau paket untuk lapisan akses data.
// DataAccessLayer.dll
public class ProductDAO {
}
Dan perpustakaan lain atau logika paket bisnis lapisan yang bergantung pada lapisan akses data.
// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO {
private ProductDAO productDAO;
}
Pembalikan ketergantungan menunjukkan hal berikut:
Modul tingkat tinggi seharusnya tidak bergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi.
Abstraksi tidak harus bergantung pada detail. Detail harus bergantung pada abstraksi.
Apa saja modul tingkat tinggi dan tingkat rendah? Modul-modul berpikir seperti perpustakaan atau paket, modul tingkat tinggi adalah modul-modul yang secara tradisional memiliki dependensi dan level rendah di mana mereka bergantung.
Dengan kata lain, modul level tinggi akan menjadi tempat tindakan dipanggil dan tingkat rendah di mana tindakan dilakukan.
Kesimpulan yang masuk akal untuk ditarik dari prinsip ini adalah bahwa seharusnya tidak ada ketergantungan antar konkret, tetapi harus ada ketergantungan pada abstraksi. Tetapi menurut pendekatan yang kami ambil, kami dapat menyalahgunakan ketergantungan investasi, tetapi abstraksi.
Bayangkan kita mengadaptasi kode kita sebagai berikut:
Kami akan memiliki pustaka atau paket untuk lapisan akses data yang mendefinisikan abstraksi.
// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{
}
Dan perpustakaan lain atau logika paket bisnis lapisan yang bergantung pada lapisan akses data.
// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO {
private IProductDAO productDAO;
}
Meskipun kami bergantung pada ketergantungan abstrak antara bisnis dan akses data tetap sama.
Untuk mendapatkan inversi dependensi, antarmuka persistensi harus didefinisikan dalam modul atau paket di mana logika atau domain tingkat tinggi ini dan tidak dalam modul tingkat rendah.
Pertama-tama tentukan apa lapisan domain itu dan abstraksi komunikasinya adalah kegigihan.
// Domain.dll
public interface IProductRepository;
using DataAccessLayer;
public class ProductBO {
private IProductRepository productRepository;
}
Setelah lapisan kegigihan tergantung pada domain, dapatkan untuk membalikkan sekarang jika ketergantungan ditentukan.
// Persistence.dll
public class ProductDAO : IProductRepository{
}
(sumber: xurxodev.com )
Penting untuk mengasimilasi konsep dengan baik, memperdalam tujuan dan manfaat. Jika kita tinggal secara mekanis dan mempelajari repositori kasus tipikal, kita tidak akan dapat mengidentifikasi di mana kita dapat menerapkan prinsip ketergantungan.
Tetapi mengapa kita membalikkan ketergantungan? Apa tujuan utama di luar contoh spesifik?
Seperti umumnya memungkinkan hal-hal yang paling stabil, yang tidak tergantung pada hal-hal yang kurang stabil, berubah lebih sering.
Jenis persistensi lebih mudah diubah, baik basis data atau teknologi untuk mengakses basis data yang sama daripada logika domain atau tindakan yang dirancang untuk berkomunikasi dengan kegigihan. Karena itu, ketergantungan ini dibalik karena lebih mudah untuk mengubah kegigihan jika perubahan ini terjadi. Dengan cara ini kita tidak perlu mengubah domain. Lapisan domain adalah yang paling stabil, oleh karena itu ia tidak boleh bergantung pada apa pun.
Tetapi tidak hanya ada contoh repositori ini. Ada banyak skenario di mana prinsip ini berlaku dan ada arsitektur berdasarkan prinsip ini.
Ada arsitektur di mana inversi ketergantungan adalah kunci dari definisinya. Di semua domain itu adalah yang paling penting dan itu adalah abstraksi yang akan menunjukkan protokol komunikasi antara domain dan sisa paket atau pustaka didefinisikan.
Dalam arsitektur bersih , domain terletak di tengah dan jika Anda melihat ke arah panah yang menunjukkan ketergantungan, jelas lapisan apa yang paling penting dan stabil. Lapisan luar dianggap alat yang tidak stabil jadi hindari tergantung padanya.
(sumber: 8thlight.com )
Ini terjadi dengan cara yang sama dengan arsitektur heksagonal, di mana domain juga terletak di bagian tengah dan port adalah abstraksi komunikasi dari domino ke arah luar. Di sini sekali lagi terbukti bahwa domain adalah ketergantungan yang paling stabil dan tradisional dibalik.
Bagi saya, Prinsip Ketergantungan Inversi, sebagaimana dijelaskan dalam artikel resmi , benar-benar merupakan upaya yang salah arah untuk meningkatkan usabilitas modul yang secara inheren kurang dapat digunakan kembali, serta cara untuk mengatasi masalah dalam bahasa C ++.
Masalah dalam C ++ adalah bahwa file header biasanya berisi deklarasi bidang pribadi dan metode. Oleh karena itu, jika modul C ++ tingkat tinggi menyertakan file header untuk modul tingkat rendah, itu akan tergantung pada detail implementasi aktual dari modul itu. Dan itu, jelas, bukan hal yang baik. Tapi ini bukan masalah dalam bahasa yang lebih modern yang umum digunakan saat ini.
Modul tingkat tinggi secara inheren kurang dapat digunakan kembali daripada modul tingkat rendah karena yang pertama biasanya lebih spesifik aplikasi / konteks daripada yang terakhir. Sebagai contoh, komponen yang mengimplementasikan layar UI adalah tingkat tertinggi dan juga sangat (sepenuhnya?) Khusus untuk aplikasi. Mencoba menggunakan kembali komponen seperti itu dalam aplikasi yang berbeda adalah kontra-produktif, dan hanya dapat menyebabkan rekayasa berlebihan.
Jadi, pembuatan abstraksi terpisah pada tingkat yang sama dari komponen A yang bergantung pada komponen B (yang tidak bergantung pada A) dapat dilakukan hanya jika komponen A benar-benar akan berguna untuk digunakan kembali dalam aplikasi atau konteks yang berbeda. Jika itu tidak terjadi, maka menerapkan DIP akan menjadi desain yang buruk.
Pada dasarnya dikatakan:
Kelas harus bergantung pada abstraksi (mis. Antarmuka, kelas abstrak), bukan detail spesifik (implementasi).
Jawaban yang baik dan contoh yang baik sudah diberikan oleh orang lain di sini.
Alasan DIP penting adalah karena memastikan prinsip OO "desain yang digabungkan secara longgar".
Objek dalam perangkat lunak Anda TIDAK boleh masuk ke hierarki di mana beberapa objek adalah yang tingkat atas, tergantung pada objek tingkat rendah. Perubahan objek tingkat rendah kemudian akan beriak ke objek tingkat atas Anda yang membuat perangkat lunak sangat rapuh untuk perubahan.
Anda ingin objek 'tingkat atas' Anda menjadi sangat stabil dan tidak rapuh untuk perubahan, oleh karena itu Anda perlu membalikkan dependensi.
Cara yang jauh lebih jelas untuk menyatakan Prinsip Ketergantungan Inversi adalah:
Modul Anda yang merangkum logika bisnis yang kompleks tidak harus bergantung langsung pada modul lain yang merangkum logika bisnis. Sebaliknya, mereka harusnya hanya bergantung pada antarmuka untuk data sederhana.
Yaitu, alih-alih mengimplementasikan kelas Anda Logic
seperti yang biasa dilakukan orang:
class Dependency { ... }
class Logic {
private Dependency dep;
int doSomething() {
// Business logic using dep here
}
}
Anda harus melakukan sesuatu seperti:
class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
private Dependency dep;
...
}
class Logic {
int doSomething(Data data) {
// compute something with data
}
}
Data
dan DataFromDependency
harus hidup dalam modul yang sama dengan Logic
, bukan dengan Dependency
.
Kenapa melakukan ini?
Dependency
berubah, Anda tidak perlu berubah Logic
.Logic
dilakukan adalah tugas yang jauh lebih sederhana: itu hanya beroperasi pada apa yang tampak seperti ADT.Logic
sekarang bisa lebih mudah diuji. Anda sekarang dapat langsung instantiate Data
dengan data palsu dan meneruskannya. Tidak perlu mengejek atau perancah tes kompleks.DataFromDependency
, yang secara langsung merujuk Dependency
, ada dalam modul yang sama dengan Logic
, maka Logic
modul masih secara langsung tergantung pada Dependency
modul pada waktu kompilasi. Per penjelasan Paman Bob tentang prinsip , menghindari itu adalah inti dari DIP. Sebaliknya, untuk mengikuti DIP, Data
harus dalam modul yang sama dengan Logic
, tetapi DataFromDependency
harus dalam modul yang sama dengan Dependency
.
Inversion of control (IoC) adalah pola desain di mana objek diserahkan ketergantungannya oleh kerangka luar, daripada meminta kerangka kerja untuk ketergantungannya.
Contoh pseudocode menggunakan pencarian tradisional:
class Service {
Database database;
init() {
database = FrameworkSingleton.getService("database");
}
}
Kode serupa menggunakan IoC:
class Service {
Database database;
init(database) {
this.database = database;
}
}
Manfaat IoC adalah:
Titik inversi ketergantungan adalah membuat perangkat lunak yang dapat digunakan kembali.
Idenya adalah bahwa alih-alih dua potong kode mengandalkan satu sama lain, mereka bergantung pada beberapa antarmuka abstrak. Kemudian Anda dapat menggunakan kembali salah satu bagian tanpa yang lain.
Cara ini paling umum dicapai adalah melalui wadah inversi kontrol (IoC) seperti Spring di Jawa. Dalam model ini, properti objek diatur melalui konfigurasi XML alih-alih objek keluar dan menemukan ketergantungannya.
Bayangkan kodesemu ini ...
public class MyClass
{
public Service myService = ServiceLocator.service;
}
MyClass secara langsung tergantung pada kelas Layanan dan kelas ServiceLocator. Ini membutuhkan keduanya jika Anda ingin menggunakannya di aplikasi lain. Sekarang bayangkan ini ...
public class MyClass
{
public IService myService;
}
Sekarang, MyClass bergantung pada antarmuka tunggal, antarmuka IService. Kami membiarkan wadah IoC benar-benar mengatur nilai variabel itu.
Jadi sekarang, MyClass dapat dengan mudah digunakan kembali dalam proyek-proyek lain, tanpa membawa ketergantungan dua kelas lainnya dengannya.
Lebih baik lagi, Anda tidak perlu menyeret dependensi MyService, dan dependensi dependensi itu, dan ... yah, Anda mendapatkan idenya.
Jika kita dapat menganggapnya sebagai suatu pemberian bahwa seorang karyawan "tingkat tinggi" di suatu perusahaan dibayar untuk pelaksanaan rencana mereka, dan bahwa rencana ini disampaikan oleh eksekusi agregat dari banyak rencana karyawan "tingkat rendah", maka kita dapat mengatakan umumnya merupakan rencana yang mengerikan jika uraian rencana karyawan tingkat tinggi dengan cara apa pun digabungkan dengan rencana spesifik karyawan tingkat bawah mana pun.
Jika seorang eksekutif tingkat tinggi memiliki rencana untuk "meningkatkan waktu pengiriman", dan menunjukkan bahwa seorang karyawan di jalur pelayaran harus minum kopi dan melakukan peregangan setiap pagi, maka rencana itu sangat berpasangan dan memiliki kohesi yang rendah. Tetapi jika rencana tersebut tidak menyebutkan karyawan tertentu, dan pada kenyataannya hanya membutuhkan "entitas yang dapat melakukan pekerjaan siap untuk bekerja", maka rencana tersebut digabungkan secara longgar dan lebih kohesif: rencana tidak tumpang tindih dan dapat dengan mudah diganti . Kontraktor, atau robot, dapat dengan mudah mengganti karyawan dan rencana tingkat tinggi tetap tidak berubah.
"Tingkat tinggi" dalam prinsip inversi ketergantungan berarti "lebih penting".
Saya bisa melihat penjelasan yang baik telah diberikan dalam jawaban di atas. Namun saya ingin memberikan beberapa penjelasan mudah dengan contoh sederhana.
Dependency Inversion Principle memungkinkan programmer untuk menghapus dependensi hardcoded sehingga aplikasi menjadi longgar dan dapat diperpanjang.
Cara mencapai ini: melalui abstraksi
Tanpa inversi ketergantungan:
class Student {
private Address address;
public Student() {
this.address = new Address();
}
}
class Address{
private String perminentAddress;
private String currentAdrress;
public Address() {
}
}
Dalam cuplikan kode di atas, objek alamat diberi kode keras. Alih-alih jika kita dapat menggunakan invensi ketergantungan dan menyuntikkan objek alamat dengan melewati konstruktor atau metode penyetel. Ayo lihat.
Dengan inversi ketergantungan:
class Student{
private Address address;
public Student(Address address) {
this.address = address;
}
//or
public void setAddress(Address address) {
this.address = address;
}
}
Ketergantungan inversi: Bergantung pada abstraksi, bukan pada konkret.
Inversi kontrol: Main vs Abstraksi, dan bagaimana Main adalah lem dari sistem.
Ini adalah beberapa posting bagus yang membicarakan ini:
https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/
https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/
https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/
Katakanlah kita memiliki dua kelas: Engineer
dan Programmer
:
Engineer Kelas memiliki ketergantungan pada kelas Programmer, Seperti di bawah ini:
class Engineer () {
fun startWork(programmer: Programmer){
programmer.work()
}
}
class Programmer {
fun work(){
//TODO Do some work here!
}
}
Pada contoh ini kelas Engineer
memiliki ketergantungan pada Programmer
kelas kami . Apa yang akan terjadi jika saya perlu mengubah Programmer
?
Jelas saya perlu mengubahnya Engineer
juga. (Wow, pada titik OCP
ini juga dilanggar)
Lalu, Apa yang harus kita bersihkan dari kekacauan ini? Jawabannya adalah abstraksi sebenarnya. Dengan abstraksi, kita dapat menghapus ketergantungan antara dua kelas ini. Sebagai contoh, saya dapat membuat sebuah Interface
untuk kelas Programmer dan mulai sekarang setiap kelas yang ingin menggunakan Programmer
harus menggunakan Interface
, Kemudian dengan mengubah kelas Programmer, kita tidak perlu mengubah kelas yang menggunakannya, Karena kami abstraksi bekas.
Catatan: DependencyInjection
dapat membantu kita untuk melakukan DIP
dan SRP
juga.
Menambahkan ke kebingungan jawaban yang umumnya baik, saya ingin menambahkan sampel kecil saya sendiri untuk menunjukkan praktik yang baik vs buruk. Dan ya, saya bukan orang yang melempar batu!
Katakanlah, Anda ingin sedikit program untuk mengubah string ke format base64 melalui konsol I / O. Inilah pendekatan naif:
class Program
{
static void Main(string[] args)
{
/*
* BadEncoder: High-level class *contains* low-level I/O functionality.
* Hence, you'll have to fiddle with BadEncoder whenever you want to change
* the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
* problems with I/O shouldn't break the encoder!
*/
BadEncoder.Run();
}
}
public static class BadEncoder
{
public static void Run()
{
Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
}
}
DIP pada dasarnya mengatakan bahwa komponen tingkat tinggi tidak boleh bergantung pada implementasi tingkat rendah, di mana "level" adalah jarak dari I / O menurut Robert C. Martin ("Clean Architecture"). Tetapi bagaimana Anda bisa keluar dari kesulitan ini? Cukup dengan membuat Encoder pusat hanya bergantung pada antarmuka tanpa mengganggu cara penerapannya:
class Program
{
static void Main(string[] args)
{
/* Demo of the Dependency Inversion Principle (= "High-level functionality
* should not depend upon low-level implementations"):
* You can easily implement new I/O methods like
* ConsoleReader, ConsoleWriter without ever touching the high-level
* Encoder class!!!
*/
GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter()); }
}
public static class GoodEncoder
{
public static void Run(IReadable input, IWriteable output)
{
output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
}
}
public interface IReadable
{
string ReadInput();
}
public interface IWriteable
{
void WriteOutput(string txt);
}
public class ConsoleReader : IReadable
{
public string ReadInput()
{
return Console.ReadLine();
}
}
public class ConsoleWriter : IWriteable
{
public void WriteOutput(string txt)
{
Console.WriteLine(txt);
}
}
Perhatikan bahwa Anda tidak perlu menyentuh GoodEncoder
untuk mengubah mode I / O - kelas itu senang dengan antarmuka I / O yang diketahuinya; implementasi tingkat rendah IReadable
dan IWriteable
tidak akan pernah mengganggunya.
GoodEncoder
contoh kedua Anda. Untuk membuat contoh DIP, Anda perlu memperkenalkan gagasan tentang apa yang "memiliki" antarmuka yang telah Anda ekstrak di sini - dan, khususnya, untuk meletakkannya dalam paket yang sama dengan GoodEncoder sementara implementasinya tetap berada di luar.
Prinsip Ketergantungan Inversi (DIP) mengatakan itu
i) Modul tingkat tinggi tidak boleh bergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi.
ii) Abstraksi tidak boleh bergantung pada detail. Detail harus bergantung pada abstraksi.
Contoh:
public interface ICustomer
{
string GetCustomerNameById(int id);
}
public class Customer : ICustomer
{
//ctor
public Customer(){}
public string GetCustomerNameById(int id)
{
return "Dummy Customer Name";
}
}
public class CustomerFactory
{
public static ICustomer GetCustomerData()
{
return new Customer();
}
}
public class CustomerBLL
{
ICustomer _customer;
public CustomerBLL()
{
_customer = CustomerFactory.GetCustomerData();
}
public string GetCustomerNameById(int id)
{
return _customer.GetCustomerNameById(id);
}
}
public class Program
{
static void Main()
{
CustomerBLL customerBLL = new CustomerBLL();
int customerId = 25;
string customerName = customerBLL.GetCustomerNameById(customerId);
Console.WriteLine(customerName);
Console.ReadKey();
}
}
Catatan: Kelas harus bergantung pada abstraksi seperti antarmuka atau kelas abstrak, bukan detail spesifik (implementasi antarmuka).