Saya pakai prinsip SOLID waktu code review dan persiapan interview. Awalnya prinsip-prinsip ini terasa abstrak buat saya. Waktu saya terapkan, prinsip-prinsip ini membantu saya mengatur kode supaya perubahan berdampak ke lebih sedikit class.
Apa Itu SOLID?
SOLID adalah akronim dari lima prinsip desain object oriented programming yang diperkenalkan oleh Robert C. Martin. Prinsip-prinsip ini membantu kamu menulis kode yang lebih gampang di-maintain, di-test, dan dikembangkan. Menurut saya prinsip-prinsip ini berguna di codebase yang lebih besar.
Memahami OOP Dulu
Pertama, saya perlu paham object oriented programming (OOP) dulu. Sebuah object punya:
- State: Data/atribut yang disimpan sebuah object
- Behavior: Method/aksi yang bisa dilakukan sebuah object
Contoh sederhana yang saya pakai untuk memahaminya:
public class Person {
// State
private String name;
private int age;
// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Behavior
public void sayHello() {
System.out.println("Hello, my name is " + name);
}
}
Contoh-contoh berikut menerapkan tiap prinsip SOLID ke masalah desain yang umum.
Single Responsibility Principle (SRP)
Satu class, satu alasan untuk berubah.
Saya pernah menulis class yang mengurus autentikasi user, mengirim email, dan membuat report. Perubahan format email jadi butuh perubahan di class yang sama. Class yang terpisah bakal mengisolasi tanggung jawab ini.
Sebuah class seharusnya cuma punya satu tanggung jawab. Pendekatan yang lebih baik:
public class UserAuthenticator {
public boolean authenticate(String username, String password) {
// Only handles authentication
}
}
public class EmailService {
public void sendEmail(String to, String subject, String body) {
// Only handles email sending
}
}
public class ReportGenerator {
public void generateReport(Data data) {
// Only handles report generation
}
}
Open Closed Principle (OCP)
Terbuka untuk ekstensi, tertutup untuk modifikasi.
Saya lagi mengerjakan kalkulator luas bangun datar. Versi pertama saya punya statement if else untuk tiap jenis shape:
public class AreaCalculator {
public double calculateArea(Shape shape) {
if (shape instanceof Rectangle) {
// calculate rectangle area
} else if (shape instanceof Circle) {
// calculate circle area
}
// Adding a new shape means modifying this class!
}
}
Lalu saya belajar soal polymorphism dan abstract class:
public abstract class Shape {
public abstract double calculateArea();
}
public class Rectangle extends Shape {
private double length;
private double width;
public double calculateArea() {
return length * width;
}
}
public class Circle extends Shape {
private double radius;
public double calculateArea() {
return Math.PI * radius * radius;
}
}
public class AreaCalculator {
public double calculateTotalArea(Shape[] shapes) {
double total = 0;
for (Shape shape : shapes) {
total += shape.calculateArea(); // Works for any Shape!
}
return total;
}
}
Sekarang saya bisa menambah class shape tanpa mengubah class shape yang udah ada.
Liskov Substitution Principle (LSP)
Subtype harus bisa menggantikan base type-nya.
Saya pernah bikin kesalahan. Saya membuat class Square yang meng-extend Rectangle, tapi Square melanggar behavior rectangle:
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
}
public class Square extends Rectangle {
// This violates LSP!
public void setWidth(int width) {
this.width = width;
this.height = width; // Square forces width = height
}
public void setHeight(int height) {
this.width = height;
this.height = height; // This breaks rectangle behavior!
}
}
Masalahnya: kode yang mengharapkan Rectangle bisa rusak kalau dikasih Square, karena Square mengubah behavior yang diharapkan.
Interface Segregation Principle (ISP)
Client nggak seharusnya bergantung pada interface yang nggak mereka pakai.
Saya pernah membuat interface besar banget yang punya method untuk semuanya:
public interface BankAccount {
void deposit(double amount);
void withdraw(double amount);
double getBalance();
void transfer(BankAccount destination, double amount);
void calculateInterest();
}
Tapi nggak semua jenis rekening butuh semua method ini. Rekening tabungan mungkin nggak butuh transfer(), dan rekening giro mungkin nggak butuh calculateInterest().
Pendekatan yang lebih baik:
public interface DepositAccount {
void deposit(double amount);
void withdraw(double amount);
double getBalance();
}
public interface SavingsAccount extends DepositAccount {
void calculateInterest();
}
public interface TransferableAccount extends DepositAccount {
void transfer(BankAccount destination, double amount);
}
Dependency Inversion Principle (DIP)
Bergantunglah pada abstraksi, bukan pada yang konkret.
Saya lagi menulis notification service dan awalnya meng-hardcode implementasi email:
public class NotificationService {
private EmailService emailService; // Depends on concrete class!
public void sendNotification(String message) {
emailService.sendEmail(message);
}
}
Ini bikin susah waktu mau menambah notifikasi SMS belakangan. Saya belajar untuk bergantung pada interface sebagai gantinya:
public interface Notification {
void send(String message);
}
public class EmailNotification implements Notification {
public void send(String message) {
// Email implementation
}
}
public class SMSNotification implements Notification {
public void send(String message) {
// SMS implementation
}
}
public class NotificationService {
private Notification notification; // Depends on abstraction!
public NotificationService(Notification notification) {
this.notification = notification;
}
public void sendNotification(String message) {
notification.send(message); // Works with any Notification!
}
}
Sekarang saya bisa mengganti implementasi atau menambah implementasi lain tanpa mengubah NotificationService.
Menggabungkan Semuanya
Waktu saya mulai menerapkan prinsip SOLID, kode saya jadi:
- Lebih gampang di-test: Tiap class punya satu tanggung jawab
- Lebih gampang dikembangkan: Fitur baru nggak butuh perubahan di kode yang udah ada
- Lebih gampang dipahami: Tanggung jawab dan relasinya jelas
- Lebih maintainable: Perubahan terisolasi di class tertentu
Ringkasan prinsip
- SRP: Satu class, satu tanggung jawab
- OCP: Kembangkan lewat inheritance/polymorphism, jangan ubah kode yang udah ada
- LSP: Subtype harus bisa menggantikan base type-nya
- ISP dan DIP: Jaga interface tetap fokus dan bergantunglah pada abstraksi, bukan pada implementasi konkret
Prinsip-prinsip ini membantu saya memisahkan tanggung jawab dan mengurangi dampak perubahan. Saya memakainya untuk me-review dependency antar class.

Memuat komentar...