Dalam pengembangan perangkat lunak, menjaga kode agar tetap mudah dirawat dan dapat diperluas merupakan tantangan yang tidak mudah. Hal ini menjadi alasan utama mengapa Prinsip SOLID dijadikan standar dalam pemrograman berorientasi objek (OOP).
Pada artikel ini, kita akan membahas prinsip kelima dalam SOLID, yaitu Dependency Inversion Principle (DIP).
Apa Itu Prinsip SOLID?
Prinsip SOLID adalah kumpulan dari lima prinsip desain dalam pemrograman berorientasi objek (OOP) yang bertujuan untuk membuat kode menjadi lebih mudah dipelihara, diperluas, dan scalable. Prinsip ini pertama kali diperkenalkan oleh Robert C. Martin (Uncle Bob) pada awal tahun 2000-an dan menjadi fondasi utama dalam pengembangan perangkat lunak modern yang bersifat modular dan berbasis kontrak.
Prinsip SOLID terdiri dari lima konsep utama:
- Single Responsibility Principle (SRP): Setiap class harus memiliki satu tanggung jawab saja.
- Open/Closed Principle (OCP): Kode harus terbuka untuk dikembangkan, tetapi tertutup untuk modifikasi.
- Liskov Substitution Principle (LSP): Objek turunan harus bisa menggantikan objek induknya tanpa mengubah perilaku yang diharapkan.
- Interface Segregation Principle (ISP): Developer tidak boleh dipaksa untuk mengimplementasikan interface yang tidak mereka gunakan.
- Dependency Inversion Principle (DIP): Class-level dependency harus diarahkan ke abstraksi, bukan ke implementasi konkret.
Prinsip SOLID sangat penting karena membantu developer dalam menulis kode yang clean, mudah dibaca, dan mudah dikelola. Dengan menerapkan prinsip ini, kode menjadi lebih fleksibel terhadap perubahan, lebih mudah untuk dikembangkan tanpa memodifikasi kode yang sudah ada, dan lebih terstruktur untuk pengembangan perangkat lunak berskala besar.
Selain itu, Prinsip SOLID juga membantu dalam mengurangi ketergantungan antar komponen, sehingga perubahan pada satu bagian kode tidak akan secara langsung mempengaruhi bagian kode lainnya.
Selain itu, dengan desain yang lebih modular, kode menjadi lebih mudah untuk diuji (testable) dan mempermudah proses debugging serta maintenance. Dengan menerapkan SOLID, developer juga dapat membangun sistem yang lebih tahan terhadap perubahan dan memiliki performa yang lebih stabil dalam jangka panjang.
Dependency Inversion Principle (DIP)
Dependency Inversion Principle (DIP) adalah prinsip kelima dalam konsep SOLID yang menyatakan bahwa modul tingkat tinggi (high-level module) tidak boleh bergantung langsung pada modul tingkat rendah (low-level module). Sebaliknya, keduanya harus bergantung pada abstraksi.
Prinsip DIP bertujuan untuk mengurangi ketergantungan antar komponen dalam kode dan meningkatkan fleksibilitas serta modularitas sistem. Dalam pengembangan perangkat lunak, modul tingkat tinggi biasanya berisi logika bisnis utama, sementara modul tingkat rendah berisi detail implementasi seperti pengambilan data dari database, pengiriman notifikasi, atau interaksi dengan API eksternal.
Jika modul tingkat tinggi bergantung langsung pada detail implementasi dari modul tingkat rendah, maka setiap perubahan pada implementasi di level rendah dapat menyebabkan perubahan besar di level tinggi, yang akan mempersulit proses pengembangan dan pemeliharaan kode.
Dengan menggunakan abstraksi sebagai penghubung antar modul, developer dapat mengganti atau memodifikasi implementasi low-level tanpa mempengaruhi logika utama di high-level module, sehingga kode menjadi lebih fleksibel dan mudah diperbarui.
Contoh Kode yang Salah (Pelanggaran DIP)
Java
class LayananNotifikasi {
public void kirimNotifikasi(String pesan) {
System.out.println("Mengirim notifikasi: " + pesan);
}
}
class Aplikasi {
private LayananNotifikasi layanan;
public Aplikasi() {
layanan = new LayananNotifikasi(); // Ketergantungan langsung ke kelas konkret
}
public void proses() {
layanan.kirimNotifikasi("Pesan baru dari aplikasi");
}
}
Python
class LayananNotifikasi:
def kirim_notifikasi(self, pesan):
print(f"Mengirim notifikasi: {pesan}")
class Aplikasi:
def __init__(self):
self.layanan = LayananNotifikasi() # Ketergantungan langsung ke class konkret
def proses(self):
self.layanan.kirim_notifikasi("Pesan baru dari aplikasi")
# Membuat objek dan menjalankan proses
app = Aplikasi()
app.proses()
Masalah pada Kode di Atas:
Apabila dilihat, class Aplikasi bergantung langsung pada LayananNotifikasi, maka:
- Jika kita ingin mengganti metode pengiriman notifikasi (misalnya melalui email atau SMS), maka kita harus mengubah kode di
Aplikasi. Tentu saja hal ini melanggar prinsip OCP. - Sulit untuk diuji, karena kita harus membuat obyek/instance dari
LayananNotifikasi. - Kode menjadi kurang fleksibel dan sulit dikembangkan.
Lumayan juga dampaknya ya jika class Aplikasi bergantung langsung pada class LayananNotifikasi.
OK sekarang kita perbaiki struktur kode programnya dengan menerapkan DIP.
Contoh Kode yang Benar (Mengikuti DIP)
Untuk memperbaiki pelanggaran tersebut, kita bisa membuat interface (abstraksi) untuk LayananNotifikasi. Selanjutnya menghubungkan Aplikasi ke interface, bukan ke class yang konkret.
Java
interface LayananNotifikasi {
void kirimNotifikasi(String pesan);
}
class LayananEmail implements LayananNotifikasi {
public void kirimNotifikasi(String pesan) {
System.out.println("Mengirim Email: " + pesan);
}
}
class Aplikasi {
private LayananNotifikasi layanan;
public Aplikasi(LayananNotifikasi layanan) {
this.layanan = layanan;
}
public void proses() {
layanan.kirimNotifikasi("Pesan baru dari aplikasi");
}
}
public class Main {
public static void main(String[] args) {
LayananNotifikasi layanan = new LayananEmail();
Aplikasi app = new Aplikasi(layanan);
app.proses();
}
}
Python
from abc import ABC, abstractmethod
class LayananNotifikasi(ABC):
@abstractmethod
def kirim_notifikasi(self, pesan):
pass
class LayananEmail(LayananNotifikasi):
def kirim_notifikasi(self, pesan):
print(f"Mengirim Email: {pesan}")
class Aplikasi:
def __init__(self, layanan):
self.layanan = layanan
def proses(self):
self.layanan.kirim_notifikasi("Pesan baru dari aplikasi")
# Membuat objek dari LayananEmail
layanan = LayananEmail()
app = Aplikasi(layanan)
app.proses()
Dengan menerapkan DIP, kode yang sebelumnya memiliki ketergantungan langsung pada detail implementasi di level rendah diubah agar hanya bergantung pada abstraksi. Dalam hal ini, modul tingkat tinggi (high-level module) hanya akan berinteraksi dengan interface atau abstract class, sehingga tidak terikat dengan implementasi konkret di level rendah.
Perbaikan tersebut memungkinkan developer dengan mudah mengganti atau memperbarui detail implementasi tanpa harus memodifikasi logika utama. Selain itu, proses pengujian menjadi lebih mudah karena pengembang dapat membuat mock atau stub dari interface tanpa harus berurusan dengan detail implementasi. Dengan demikian, kode menjadi lebih modular dan reusable, karena pengembangan fitur baru atau penggantian teknologi dapat dilakukan tanpa mengganggu struktur utama kode. Perbaikan ini juga meningkatkan skalabilitas sistem karena komponen dapat diatur ulang atau diperbarui tanpa menimbulkan efek domino pada sistem secara keseluruhan.
Strategi Implementasi DIP
Untuk menerapkan Dependency Inversion Principle dengan baik, seorang pengembang perlu membuat abstraksi dalam bentuk interface atau abstract class yang berfungsi sebagai penghubung antara modul tingkat tinggi dan modul tingkat rendah. Hal ini disebabkan karena secara prinsip modul tingkat tinggi tidak boleh membuat obyek/instance langsung dari class konkret, melainkan harus menerima dependensi dari luar.
Ketergantungan pada class konkret harus digantikan dengan ketergantungan pada abstraksi, sehingga implementasi detail di modul tingkat rendah dapat dengan mudah diganti atau diperbarui tanpa mempengaruhi modul tingkat tinggi.
Kapan Class Butuh DIP?
Class yang membutuhkan Dependency Inversion Principle biasanya memiliki ketergantungan langsung pada class konkret di level bawah, yang menyebabkan kode menjadi rapuh terhadap perubahan. Jika suatu class sering kali mengalami perubahan hanya karena ada modifikasi pada detail implementasi di level rendah, maka ini adalah indikasi kuat bahwa class tersebut perlu dipisahkan dari ketergantungan langsung dengan menggunakan abstraksi.
Class yang sulit untuk diuji karena memiliki banyak ketergantungan pada class konkret juga memerlukan penerapan DIP.
Selain itu, class yang memiliki banyak ketergantungan yang sulit dikonfigurasi ulang atau sulit untuk diganti juga merupakan kandidat yang ideal untuk penerapan DIP.


