Contoh Rust Dependency Inversion Principle (DIP)
Dependency Injection adalah teknik di mana objek hanya menyediakan dependencies (ketergantungan) melalui konstruktor, metode, atau langsung ke properti. Dalam project ini, DataManager
menerima sebuah implementasi dari trait Database
sebagai dependency.
Berikut adalah penjelasan lebih lanjut:
- Abstraksi dengan Trait: Trait
Database
digunakan sebagai abstraksi untuk menggambarkan perilaku sebuah database. Ini memungkinkan kita untuk mendefinisikan metode yang harus diimplementasikan oleh setiap jenis database.
// Trait untuk mendefinisikan perilaku dari sebuah database
trait Database {
fn connect(&self);
fn query(&self, query: &str) -> Vec<String>;
}
- Implementasi Konkret:
MySQLDatabase
danPostgreSQLDatabase
merupakan implementasi konkret dari sifatDatabase
. Mereka mendefinisikan bagaimana operasi basis data dilakukan untuk basis data MySQL dan PostgreSQL.
// Implementasi konkret dari trait Database untuk MySQL
struct MySQLDatabase;
impl Database for MySQLDatabase {
fn connect(&self) {
println!("Menghubungkan ke database MySQL...");
}
fn query(&self, query: &str) -> Vec<String> {
println!("Menjalankan query: {}", query);
// Melakukan query sebenarnya dan mengembalikan hasilnya
vec!["Hasil 1".to_string(), "Hasil 2".to_string()]
}
}
// Implementasi konkret dari trait Database untuk PostgreSQL
struct PostgreSQLDatabase;
impl Database for PostgreSQLDatabase {
fn connect(&self) {
println!("Menghubungkan ke database PostgreSQL...");
}
fn query(&self, query: &str) -> Vec<String> {
println!("Menjalankan query: {}", query);
// Melakukan query sebenarnya dan mengembalikan hasilnya
vec!["Hasil 3".to_string(), "Hasil 4".to_string()]
}
}
- Penggunaan Generic Type Parameter:
DataManager
menggunakan parameter tipe umumT
yang terikat dengan sifatDatabase
. Ini berarti bahwaDataManager
dapat bekerja dengan semua jenis database yang mengimplementasikan sifatDatabase
.
// Modul tingkat tinggi yang bergantung pada trait Database
struct DataManager<T: Database> {
database: T,
}
impl<T: Database> DataManager<T> {
fn new(database: T) -> Self {
DataManager { database }
}
fn perform_query(&self, query: &str) -> Vec<String> {
self.database.connect();
self.database.query(query)
}
}
- Dependency Injection:
DataManager
tidak membuat instance dariMySQLDatabase
atauPostgreSQLDatabase
secara langsung. Sebaliknya, sebuah instance dariDatabase
(baikMySQLDatabase
atauPostgreSQLDatabase
) diinjeksikan ke dalamDataManager
. Ini berarti bahwaDataManager
tidak bergantung secara langsung pada implementasi database tertentu, tetapi hanya pada perilaku yang ditentukan oleh propertiDatabase
.
let mysql_database = MySQLDatabase;
let mysql_data_manager = DataManager {
database: mysql_database,
}; // bisa juga ditulis DataManager::new(mysql_database);
- Fleksibilitas: Dengan ini, kita dapat dengan mudah mengganti implementasi database tanpa mengubah kode
DataManager
. Sebagai contoh, jika kita ingin beralih dari MySQL ke PostgreSQL, kita hanya perlu mengubah instanceDatabase
yang diinjeksikan ke dalamDataManager
.
let postgresql_database = PostgreSQLDatabase;
let postgresql_data_manager = DataManager::new(postgresql_database); // bisa juga ditulis DataManager { database: postgresql_database };