🦉
Programming Notes
  • My Programming Notes
  • CKA Exam Preparation
    • Certified Kubernetes Administrator
    • Setup Minikube
    • Network Design Principles
    • Role-Based Access Control (RBAC)
    • Namespace
    • Resource Quota
    • Pod
    • Deployment
    • Deployment: Rollout
    • ConfigMap
    • Service
    • Service: kubectl expose
    • Pod: Resources Management
    • Pod & Container: Quality of Service Class
    • Pod & Container: Probes
    • Limit Range
    • Scaling: Manual
    • Scaling: Horizontal Pod Autoscaler
    • Persistent Volume & Claim
    • Secret
    • Ingress: Routing
    • Ingress: TLS
    • Ingress: Rate Limit
    • Ingress: Basic Auth
    • Ingress: CRD (Custom Resource Definition)
    • Job
    • CronJob
    • Mutli-Node Cluster
  • Golang
    • Generics
    • Context
    • Goroutines and Channels in Go
    • Goroutine: Concurrency vs Parallelism
    • Goroutine: Performance & Tradeoffs
    • JSON: omitzero
  • Rust
    • Arrays & Slices
    • Closures
    • Generics & Traits
    • Iterators
    • Run Code Simultaneously
    • String vs &str
    • Tests
    • Rustlings Exercises
      • Variables
      • Functions
      • If
      • Primitive Types
      • Vectors
      • Move Semantics
      • Structs
      • Enums and Matching Pattern
      • Strings
      • Modules
      • Hashmaps
      • Options
      • Error Handling
      • Generics
      • Traits
      • Lifetimes
      • Tests
      • Iterators
      • Smart Pointers
      • Threads
      • Macros
      • Quiz 1
      • Quiz 2
      • Quiz 3
  • Software Engineering
    • CAP Theorem
    • Circuit Breaker
    • Decoupling
    • GraphQL: Query Caching
    • HMAC Signature
    • Idempotency
    • Monolith VS Microservice
    • OWASP Top 10 2021
    • PCI DSS
    • PostgreSQL: Partitioning
    • PostgreSQL: Replication
    • Protobuf & gRPC
    • Redis: Streams
    • Resource Scaling
    • Signed URL
    • SOLID
    • Stack VS Heap
    • Stateful VS Stateless
  • Site Reliability Engineering
    • Chaos Engineering
    • Distributed Tracing
    • Kubernetes (k8s)
    • SLA, SLO, and SLI Metrics
    • Site Reliability Engineer
  • Others
    • FFMPEG Cheat sheet
Powered by GitBook
On this page
  • Single Responsibility Principle (SRP)
  • Open/Closed Principle (OCP)
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle (ISP)
  • Dependency Inversion Principle (DIP)
  • Summary
  1. Software Engineering

SOLID

is a set of five design principles introduced by Robert C. Martin (Uncle Bob) to make software designs more maintainable, scalable, and robust. It stands for:

  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)

Definition: A class (or struct) should have one and only one reason to change.

Before applying 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)
}

After applying 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)
}

Explanation: Each struct has a single responsibility now: one generates the report, and another saves it.

Open/Closed Principle (OCP)

Definition: Software entities should be open for extension but closed for modification.

Before 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
}

After OCP (Extensible with interfaces):

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()
}

Explanation: New shapes can be added by implementing the Shape interface without modifying AreaCalculator.

Liskov Substitution Principle (LSP)

Definition: Subtypes must be substitutable for their base types without altering the correctness of the program.

Before LSP:

type Bird struct{}

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

type Penguin struct {
    Bird
}

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

After 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
}

Explanation: The Bird interface represents general behavior, and specific birds implement it in ways that don't break substitutability.

Interface Segregation Principle (ISP)

Definition: Clients should not be forced to depend on methods they do not use.

Before 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() {
    // Robots don't eat
    panic("Robots don't eat!")
}

After 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: Robots don't eat!
}

Explanation: Interfaces are split based on specific needs, so RobotWorker doesn’t need to implement Eat().

Dependency Inversion Principle (DIP)

Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions.

Before 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...")
}

After 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...
}

Explanation: The Service depends on the Database interface, not specific implementations. This makes it easy to switch databases.

Summary

Principle
Description
Example

SRP

One responsibility per class/module

Separate report generation and saving logic

OCP

Open for extension, closed for modification

Add new shapes without changing the calculator

LSP

Subtypes replace base types without breaking behavior

Penguins swim instead of fly

ISP

Split interfaces to avoid unused methods

Separate Work and Eat interfaces

DIP

Depend on abstractions, not concretions

Service uses Database interface, not specific DB

These principles make code cleaner, more maintainable, and easier to test.

PreviousSigned URLNextStack VS Heap

Last updated 4 months ago