SOLID adalah 5 prinsip desain object-oriented yang membuat kode lebih mudah di-maintain, dipahami, dan diperluas. Dicetuskan Robert C. Martin.
S — Single Responsibility Principle (SRP) Setiap class hanya punya satu alasan untuk berubah.
// BURUK: UserService melakukan terlalu banyak hal
class UserService {
saveUser(user) { /* simpan ke DB */ }
sendWelcomeEmail(user) { /* kirim email */ }
generateReport(users) { /* buat laporan */ }
}
// BERSIH: pisah tanggung jawab
class UserRepository { saveUser(user) { /* simpan ke DB */ } }
class EmailService { sendWelcomeEmail(user) { /* kirim email */ } }
class UserReportGenerator { generateReport(users) { /* buat laporan */ } }
O — Open/Closed Principle (OCP) Terbuka untuk ekstensi, tertutup untuk modifikasi.
// BURUK: harus ubah calculateArea() setiap tambah shape baru
function calculateArea(shape) {
if (shape.type === "circle") return Math.PI * shape.radius ** 2;
if (shape.type === "rectangle") return shape.width * shape.height;
// Harus tambah if baru setiap ada shape baru!
}
// BERSIH: tiap shape tahu cara hitung areanya sendiri
class Circle {
constructor(radius) { this.radius = radius; }
area() { return Math.PI * this.radius ** 2; }
}
class Rectangle {
constructor(width, height) { this.width = width; this.height = height; }
area() { return this.width * this.height; }
}
class Triangle {
constructor(base, height) { this.base = base; this.height = height; }
area() { return 0.5 * this.base * this.height; }
}
// Tambah shape baru? Buat class baru — tidak ubah kode yang sudah ada
function calculateArea(shape) { return shape.area(); }
L — Liskov Substitution Principle (LSP) Subclass harus bisa menggantikan parent class tanpa merusak program.
// BURUK: Square melanggar contract Rectangle
class Rectangle {
setWidth(w) { this.width = w; }
setHeight(h) { this.height = h; }
area() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(w) { this.width = w; this.height = w; } // melanggar LSP!
setHeight(h) { this.width = h; this.height = h; } // melanggar LSP!
}
// rect.setWidth(5); rect.setHeight(4); // area harusnya 20
// tapi kalau rect = new Square(): area = 16 (bukan 20!)
// BERSIH: tidak ada hierarki yang melanggar
class Shape { area() { throw new Error("Must implement area()"); } }
class Rectangle extends Shape {
constructor(w, h) { super(); this.width = w; this.height = h; }
area() { return this.width * this.height; }
}
class Square extends Shape {
constructor(side) { super(); this.side = side; }
area() { return this.side ** 2; }
}
I — Interface Segregation Principle (ISP) Jangan paksa class implement interface yang tidak diperlukan.
// BURUK: Animal dipaksa implement semua kemampuan
class Animal {
fly() { throw new Error("Not implemented"); }
swim() { throw new Error("Not implemented"); }
run() { throw new Error("Not implemented"); }
}
class Dog extends Animal {
fly() { throw new Error("Anjing tidak bisa terbang!"); } // dipaksa
swim() { /* ... */ }
run() { /* ... */ }
}
// BERSIH: pisah interface sesuai kemampuan
const Swimmer = { swim() {} };
const Runner = { run() {} };
const Flyer = { fly() {} };
class Dog {
swim() { /* ... */ }
run() { /* ... */ }
}
class Bird {
fly() { /* ... */ }
run() { /* ... */ }
}
D — Dependency Inversion Principle (DIP) Bergantung pada abstraksi, bukan implementasi konkret.
// BURUK: OrderService bergantung langsung pada MySQL
class OrderService {
constructor() {
this.db = new MySQLDatabase(); // tightly coupled!
}
saveOrder(order) { this.db.save(order); }
}
// BERSIH: bergantung pada abstraksi (interface)
class OrderService {
constructor(database) { // inject dependency
this.database = database; // bisa MySQL, PostgreSQL, MongoDB, atau mock
}
saveOrder(order) { this.database.save(order); }
}
// Penggunaan
const orderService = new OrderService(new MySQLDatabase());
// Testing
const orderService = new OrderService(new MockDatabase());
Rangkuman SOLID:
| Prinsip | Inti | Pertanyaan |
|---|---|---|
| SRP | Satu tanggung jawab | "Ada berapa alasan class ini berubah?" |
| OCP | Extend, jangan modifikasi | "Perlu ubah kode lama untuk tambah fitur baru?" |
| LSP | Subclass bisa gantikan parent | "Subclass berperilaku sama seperti parent?" |
| ISP | Interface kecil dan spesifik | "Ada method yang tidak relevan di-implement?" |
| DIP | Bergantung pada abstraksi | "Class ini tightly coupled ke implementasi?" |
🎭 Analogi sehari-hari
SOLID itu kayak 5 aturan dasar arsitek bangun gedung. Tiap aturan keluar dari pengalaman pahit — gedung roboh karena pondasi gak kuat (DIP), gedung gak bisa di-renovasi karena dinding load-bearing di mana-mana (OCP), satu rusak semua kena (SRP), pintu darurat yang gak bisa dilewatin difabel (LSP), kunci master yang punya akses berlebihan (ISP). Bangunan kecil (rumah hobby) mungkin gak perlu ikutin semua aturan ketat — overkill. Tapi gedung 50 lantai dengan ribuan penghuni? Wajib. Software sama: prototype 1 minggu, gak butuh SOLID ketat. Sistem enterprise dengan ratusan dev + decade-long maintenance? SOLID = pondasi yang nyelamatin proyek dari kollaps berulang.
⚠️ Jebakan yang sering ditemui
- Apply SOLID dogmatis tanpa konteks — over-engineer simple project. SOLID = guideline buat sistem yang grow.
- Misinterpret SRP — pikir "satu fungsi = satu line". SRP = "satu alasan untuk berubah", bisa 50 baris kalau semua tentang one concern.
- OCP via inheritance — bikin hierarki class dalam buat add fitur. Modern: composition + dependency injection lebih flexible.
- LSP violated diam-diam — Subclass throw "Not Supported" = violasi. Kalau Penguin extend Bird tapi
fly()throw error, Penguin BUKAN Bird (di model program ini). - ISP terlalu kecil — extract jadi 100 interface 1-method = overkill. Group by concern, not by method count.
- DIP tanpa pikir abstraction — inject
Databaseinterface yang punya 50 method = bukan good abstraction. Interface harus stable + minimal. - Pikir SOLID = kompleksitas — sebaliknya. SOLID yang DIAPLIKASI BENAR bikin kode lebih simple per unit.
Kapan Apply SOLID
Project size:
- Toy / prototype 1 minggu → SOLID = overkill
- App kecil 1-3 dev → SRP + DIP (basic), sisanya situational
- App medium tim 5-15 dev → SOLID 80% applied
- Enterprise 20+ dev tahunan → SOLID strict, plus pattern lain (DDD, hexagonal)
SOLID dengan Hooks (Modern React)
SOLID originally OOP, tapi prinsip applicable di functional style juga:
- SRP: Custom hooks yang fokus 1 concern (
useToggle,useFetch) - OCP: HOC / wrapper untuk extend behavior tanpa modify komponen asli
- LSP: Component contract konsisten (
<Button>selalu render button-like) - ISP: Props interface kecil per concern, bukan giant object
- DIP: Pass dependencies via props/context (testable + flexible)
🎯 Apply SOLID atau biarkan?
- Public API yang banyak tim consume → SOLID strict ✓
- Service core yang kena bug terus → apply SOLID untuk fix root cause
- Module yang sering diubah requirement-nya → SOLID (terutama OCP)
- Library / framework code → SOLID strict ✓
- Quick prototype, validation MVP → biarin, refactor pas product-market fit
- Static utility functions → gak perlu SOLID, function biasa cukup
- Class dengan 1-2 method → SRP + skip yang lain
Aturan: SOLID pas problem-nya datang (pain dari maintenance), bukan dari awal sebagai dogma.
TL;DR: SOLID = 5 prinsip OOP design (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) buat sistem yang scalable + maintainable. Apply situasional — overkill di prototype, wajib di enterprise. SRP paling fundamental, DIP paling impact testability. Modern: applicable juga di functional/React style. Aturan: pakai SOLID sebagai TOOL solve pain konkret, bukan dogma yang dipaksa di mana-mana.