Portal/Notes πŸ“
Programming

SOLID

Lima prinsip desain oleh Robert C. Martin β€” Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion.

Versi Bahasa Indonesia. Baca dalam Bahasa Inggris

adalah seperangkat lima prinsip desain yang diperkenalkan oleh Robert C. Martin (Uncle Bob) untuk membuat desain perangkat lunak lebih mudah dipelihara, diskalakan, dan robust. Ini adalah singkatan dari:

  1. Single Responsibility Principle (SRP)
  2. Open/Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

Single Responsibility Principle (SRP)

Definisi: Sebuah kelas (atau struct) harus memiliki satu dan hanya satu alasan untuk berubah.

Sebelum menerapkan SRP:

type Report struct{}

func (r Report) GenerateReport() {
    fmt.Println("Generating report...")
}

func (r Report) SaveToFile(filename string) {
    fmt.Println("Saving report to file:", filename)
}

Setelah menerapkan SRP:

type ReportGenerator struct{}

func (r ReportGenerator) GenerateReport() {
    fmt.Println("Generating report...")
}

type FileSaver struct{}

func (f FileSaver) SaveToFile(filename string) {
    fmt.Println("Saving to file:", filename)
}

Penjelasan: Setiap struct sekarang memiliki satu tanggung jawab: satu menghasilkan laporan, dan yang lainnya menyimpannya.

Open/Closed Principle (OCP)

Definisi: Entitas perangkat lunak harus terbuka untuk ekstensi tetapi tertutup untuk modifikasi.

Sebelum OCP:

type AreaCalculator struct{}

func (a AreaCalculator) CalculateArea(shape string, dimensions ...float64) float64 {
    if shape == "circle" {
        return 3.14 * dimensions[0] * dimensions[0]
    } else if shape == "rectangle" {
        return dimensions[0] * dimensions[1]
    }
    return 0
}

Setelah OCP (Dapat diperluas dengan interface):

type Shape interface {
    Area() float64
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}

type Rectangle struct {
    Length, Width float64
}

func (r Rectangle) Area() float64 {
    return r.Length * r.Width
}

type AreaCalculator struct{}

func (a AreaCalculator) CalculateArea(s Shape) float64 {
    return s.Area()
}

Penjelasan: Bentuk baru dapat ditambahkan dengan mengimplementasikan interface Shape tanpa memodifikasi AreaCalculator.

Liskov Substitution Principle (LSP)

Definisi: Subtipe harus dapat disubstitusi untuk tipe dasarnya tanpa mengubah kebenaran program.

Sebelum LSP:

type Bird struct{}

func (b Bird) Fly() {
    fmt.Println("Flying")
}

type Penguin struct {
    Bird
}

func (p Penguin) Fly() {
    panic("Penguins cannot fly!")
}

Setelah LSP:

type Bird interface {
    Move()
}

type Sparrow struct{}

func (s Sparrow) Move() {
    fmt.Println("Flying")
}

type Penguin struct{}

func (p Penguin) Move() {
    fmt.Println("Swimming")
}

func main() {
    var bird Bird
    bird = Sparrow{}
    bird.Move() // Output: Flying

    bird = Penguin{}
    bird.Move() // Output: Swimming
}

Penjelasan: Interface Bird mewakili perilaku umum, dan burung tertentu mengimplementasikannya dengan cara yang tidak merusak substitutability.

Interface Segregation Principle (ISP)

Definisi: Klien tidak boleh dipaksa untuk bergantung pada method yang tidak mereka gunakan.

Sebelum ISP:

type Worker interface {
    Work()
    Eat()
}

type HumanWorker struct{}

func (h HumanWorker) Work() {
    fmt.Println("Working...")
}

func (h HumanWorker) Eat() {
    fmt.Println("Eating lunch...")
}

type RobotWorker struct{}

func (r RobotWorker) Work() {
    fmt.Println("Working...")
}

func (r RobotWorker) Eat() {
    // Robot tidak makan
    panic("Robots don't eat!")
}

Setelah ISP:

type Worker interface {
    Work()
}

type Eater interface {
    Eat()
}

type HumanWorker struct{}

func (h HumanWorker) Work() {
    fmt.Println("Working...")
}

func (h HumanWorker) Eat() {
    fmt.Println("Eating lunch...")
}

type RobotWorker struct{}

func (r RobotWorker) Work() {
    fmt.Println("Working...")
}

func main() {
    human := HumanWorker{}
    robot := RobotWorker{}

    human.Work() // Output: Working...
    human.Eat()  // Output: Eating lunch...

    robot.Work() // Output: Working...
    // robot.Eat() // Error: Robot tidak makan!
}

Penjelasan: Interface dipecah berdasarkan kebutuhan spesifik, sehingga RobotWorker tidak perlu mengimplementasikan Eat().

Dependency Inversion Principle (DIP)

Definisi: Modul tingkat tinggi tidak boleh bergantung pada modul tingkat rendah. Keduanya harus bergantung pada abstraksi.

Sebelum DIP:

type MySQLDatabase struct{}

func (db MySQLDatabase) Connect() {
    fmt.Println("Connecting to MySQL...")
}

type Service struct {
    Database MySQLDatabase
}

func (s Service) PerformTask() {
    s.Database.Connect()
    fmt.Println("Performing task...")
}

Setelah DIP:

type Database interface {
    Connect()
}

type MySQLDatabase struct{}

func (db MySQLDatabase) Connect() {
    fmt.Println("Connecting to MySQL...")
}

type PostgreSQLDatabase struct{}

func (db PostgreSQLDatabase) Connect() {
    fmt.Println("Connecting to PostgreSQL...")
}

type Service struct {
    Database Database
}

func (s Service) PerformTask() {
    s.Database.Connect()
    fmt.Println("Performing task...")
}

func main() {
    mysql := MySQLDatabase{}
    postgres := PostgreSQLDatabase{}

    service := Service{Database: mysql}
    service.PerformTask() // Output: Connecting to MySQL... Performing task...

    service.Database = postgres
    service.PerformTask() // Output: Connecting to PostgreSQL... Performing task...
}

Penjelasan: Service bergantung pada interface Database, bukan implementasi spesifik. Ini memudahkan untuk mengganti database.

Ringkasan

PrinsipDeskripsiContoh
SRPSatu tanggung jawab per kelas/modulPisahkan logika pembuatan dan penyimpanan laporan
OCPTerbuka untuk ekstensi, tertutup untuk modifikasiTambahkan bentuk baru tanpa mengubah kalkulator
LSPSubtipe menggantikan tipe dasar tanpa merusak perilakuPenguin berenang, bukan terbang
ISPPisahkan interface untuk menghindari method yang tidak digunakanPisahkan interface Work dan Eat
DIPBergantung pada abstraksi, bukan implementasi konkretService menggunakan interface Database, bukan DB spesifik

Prinsip-prinsip ini membuat kode lebih bersih, lebih mudah dipelihara, dan lebih mudah diuji.


Edit on GitHub

Last updated on

SOLID | Faisal Affan