SOLID di Go
1. Single Responsibility Principle (SRP)
Prinsip: Satu class/module hanya memiliki satu tanggung jawab. Aplikasi Dunia Nyata: Sistem manajemen pengguna di aplikasi e-commerce.
// ❌ Melanggar SRP: User struct menangani data & validasi
type User struct {
Name string
Email string
}
func (u *User) Save() error {
// Logika simpan ke database
return nil
}
func (u *User) ValidateEmail() bool {
// Logika validasi email
return strings.Contains(u.Email, "@")
}
// ✅ Memenuhi SRP: Pemisahan tanggung jawab
type User struct {
Name string
Email string
}
type UserRepository struct{} // Tanggung jawab: akses data
func (r *UserRepository) Save(u *User) error { /* ... */ }
type UserValidator struct{} // Tanggung jawab: validasi
func (v *UserValidator) ValidateEmail(u *User) bool { /* ... */ }
Keuntungan:
Perubahan validasi tidak memengaruhi logika penyimpanan data.
Kode lebih mudah di-maintain dan diuji.
2. Open/Closed Principle (OCP)
Prinsip: Software entities terbuka untuk ekstensi, tetapi tertutup untuk modifikasi. Aplikasi Dunia Nyata: Sistem pembayaran dengan metode pembayaran yang beragam.
// ❌ Melanggar OCP: Menambah metode pembayaran baru = modifikasi PaymentProcessor
type PaymentProcessor struct{}
func (p *PaymentProcessor) ProcessPayment(method string, amount float64) {
switch method {
case "credit_card":
// Logika pembayaran kartu kredit
case "paypal":
// Logika pembayaran PayPal
}
}
// ✅ Memenuhi OCP: Gunakan interface untuk ekstensi
type PaymentMethod interface {
Process(amount float64) error
}
type CreditCard struct{} // Implementasi baru tanpa ubah PaymentProcessor
func (c *CreditCard) Process(amount float64) error { /* ... */ }
type PayPal struct{} // Implementasi baru
func (p *PayPal) Process(amount float64) error { /* ... */ }
type PaymentProcessor struct {
methods []PaymentMethod
}
func (p *PaymentProcessor) ProcessPayment(amount float64) error {
for _, method := range p.methods {
if err := method.Process(amount); err != nil {
return err
}
}
return nil
}
Keuntungan:
Tambah metode pembayaran baru (misal:
GoPay
) tanpa ubah kodePaymentProcessor
.Kode lebih fleksibel terhadap perubahan bisnis.
3. Liskov Substitution Principle (LSP)
LSP adalah prinsip ke-3 dari SOLID. Intinya:
Sebuah subclass atau implementasi harus bisa menggantikan superclass/interface tanpa mengubah perilaku yang diharapkan dalam program.
Artinya, kalau kita punya fungsi yang bekerja dengan interface X
, maka semua implementasi dari interface X
harus bisa dipakai tanpa bikin bug atau perilaku aneh.
Analogi sederhana
Bayangkan ada interface Bird
dengan method Fly()
.
Kalau kita punya Sparrow
(burung pipit), dia bisa terbang.
Kalau kita tambahkan Penguin
, dia tidak bisa terbang.
Kalau kita masukkan Penguin
ke dalam fungsi yang ekspektasinya semua Bird
bisa Fly()
, pasti ada masalah → inilah pelanggaran LSP.
Contoh di Golang
Contoh yang Melanggar LSP
package main
import "fmt"
type Bird interface {
Fly()
}
type Sparrow struct{}
func (s Sparrow) Fly() {
fmt.Println("Sparrow terbang...")
}
type Penguin struct{}
func (p Penguin) Fly() {
panic("Penguin tidak bisa terbang!") // ❌ pelanggaran LSP
}
func MakeBirdFly(b Bird) {
b.Fly()
}
func main() {
var s Bird = Sparrow{}
var p Bird = Penguin{}
MakeBirdFly(s) // OK
MakeBirdFly(p) // runtime panic ❌
}
Kenapa ini melanggar LSP?
Karena Penguin
secara konsep tidak sesuai dengan kontrak Bird
yang punya Fly()
.
Ketika dipakai, malah menyebabkan error → program tidak bisa bekerja dengan substitusi Penguin
.
Contoh yang Benar (Mematuhi LSP) Pisahkan interface berdasarkan kemampuan:
package main
import "fmt"
// Bird umum
type Bird interface {
Walk()
}
type FlyingBird interface {
Bird
Fly()
}
type Sparrow struct{}
func (s Sparrow) Walk() {
fmt.Println("Sparrow berjalan...")
}
func (s Sparrow) Fly() {
fmt.Println("Sparrow terbang...")
}
type Penguin struct{}
func (p Penguin) Walk() {
fmt.Println("Penguin berjalan...")
}
func MakeBirdWalk(b Bird) {
b.Walk()
}
func MakeBirdFly(b FlyingBird) {
b.Fly()
}
func main() {
var s FlyingBird = Sparrow{}
var p Bird = Penguin{}
MakeBirdWalk(s) // ✅ Sparrow bisa jalan
MakeBirdFly(s) // ✅ Sparrow bisa terbang
MakeBirdWalk(p) // ✅ Penguin bisa jalan
// MakeBirdFly(p) // ❌ compile error, bagus! Tidak bisa karena penguin bukan FlyingBird
}
Dengan memisahkan interface, Penguin
tetap bisa jadi Bird
tanpa memaksa punya Fly()
.
Program lebih aman dan konsisten dengan LSP.
Inti LSP
Gunakan interface kecil dan spesifik (mirip Interface Segregation Principle).
Jangan memaksa implementasi melanggar kontrak interface.
Fungsi yang menerima interface harus bekerja untuk semua implementasinya tanpa perlu tahu implementasi spesifiknya.
Jadi kunci LSP di Golang: jangan bikin interface yang memaksa implementasi melakukan hal yang tidak cocok dengan nature-nya.
Mau saya bikinkan contoh lain yang lebih ke use-case real (misalnya transaksi pembayaran, user repository, dsb.) biar lebih kebayang selain contoh "Bird"?
4. Interface Segregation Principle (ISP)
Prinsip: Klien tidak boleh dipaksa bergantung pada interface yang tidak digunakan. Aplikasi Dunia Nyata: Sistem IoT dengan device yang memiliki fitur berbeda.
// ❌ Melanggar ISP: Device dengan fitur terlalu banyak
type SmartDevice interface {
TurnOn()
TurnOff()
GetTemperature() float64 // Tidak semua device punya sensor suhu
CaptureImage() []byte // Tidak semua device punya kamera
}
type SmartBulb struct{} // ❌ Dipaksa implementasi GetTemperature & CaptureImage
func (b *SmartBulb) TurnOn() {}
func (b *SmartBulb) TurnOff() {}
func (b *SmartBulb) GetTemperature() float64 { return 0 } // Tidak relevan
func (b *SmartBulb) CaptureImage() []byte { return nil } // Tidak relevan
// ✅ Memenuhi ISP: Pecah interface menjadi fitur spesifik
type PowerSwitch interface {
TurnOn()
TurnOff()
}
type TemperatureSensor interface {
GetTemperature() float64
}
type Camera interface {
CaptureImage() []byte
}
type SmartBulb struct{} // Hanya implementasi yang relevan
func (b *SmartBulb) TurnOn() {}
func (b *SmartBulb) TurnOff() {}
type Thermostat struct{} // Implementasi fitur yang dimiliki
func (t *Thermostat) TurnOn() {}
func (t *Thermostat) TurnOff() {}
func (t *Thermostat) GetTemperature() float64 { return 25.5 }
Keuntungan:
Device hanya implementasi fitur yang dimiliki.
Menghindari "noise" dari metode tidak relevan.
5. Dependency Inversion Principle (DIP)
Prinsip:
High-level module tidak bergantung pada low-level module.
Keduanya bergantung pada abstraksi (interface).
Abstraksi tidak bergantung pada detail.
Aplikasi Dunia Nyata: Service layer yang bergantung pada repository.
// ❌ Melanggar DIP: OrderService langsung bergantung pada MySQLRepository
type MySQLRepository struct{}
func (r *MySQLRepository) SaveOrder(order Order) error {
// Simpan ke MySQL
return nil
}
type OrderService struct {
repo *MySQLRepository // Ketergantungan langsung ke implementasi
}
func (s *OrderService) CreateOrder(order Order) error {
return s.repo.SaveOrder(order)
}
// ✅ Memenuhi DIP: Gunakan interface sebagai abstraksi
type OrderRepository interface {
SaveOrder(order Order) error
}
type MySQLRepository struct{} // Low-level module
func (r *MySQLRepository) SaveOrder(order Order) error { /* ... */ }
type OrderService struct { // High-level module
repo OrderRepository // Bergantung pada abstraksi
}
func NewOrderService(repo OrderRepository) *OrderService {
return &OrderService{repo: repo}
}
func (s *OrderService) CreateOrder(order Order) error {
return s.repo.SaveOrder(order)
}
Keuntungan:
Mudah ganti database (misal dari MySQL ke PostgreSQL) tanpa ubah
OrderService
.Kode lebih mudah diuji (bisa mock
OrderRepository
).
Kesimpulan: Manfaat SOLID di Go
Maintainability: Perubahan pada satu fitur tidak memengaruhi fitur lain (SRP, ISP).
Extensibility: Tambah fitur baru tanpa ubah kode existing (OCP, DIP).
Reliability: Substitusi komponen aman tanpa error (LSP).
Testability: Dependency injection (DIP) memudahkan pembuatan unit test.
Scalability: Arsitektur modular mendukung pertumbuhan aplikasi.
Best Practice di Go:
Gunakan interface untuk abstraksi (OCP, DIP, ISP).
Hindari struct dengan tanggung jawab ganda (SRP).
Pastikan implementasi interface konsisten (LSP).
Lakukan dependency injection melalui constructor/parameter.
Last updated