Skip to content

SOLID 원칙

Published: at 오전 11:02

Table of contents

Open Table of contents

단일 책임 원칙 (Single Responsibility Principle, SRP)

하나의 모듈은 하나의, 오직 하나의 액터(변경 이유)만을 책임져야한다.

class User {
    fun save() {
        // save user
    }
    fun delete() {
        // delete user
    }
    fun sendEmail() {
        // send email
    }
}

위 코드는 단일 책임 원칙을 위반합니다.

User 클래스는 사용자의 정보를 저장하고 삭제하는 책임과 이메일을 보내는 책임을 가지고 있습니다.

이를 분리하면 아래와 같습니다.

class User {
    fun save() {
        // save user
    }
    fun delete() {
        // delete user
    }
}

class EmailSender {
    fun sendEmail() {
        // send email
    }
}

개방-폐쇄 원칙 (Open/Closed Principle, OCP)

소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만, 수정에 대해서는 닫혀 있어야 한다.

class Rectangle {
    fun draw() {
        // draw rectangle
    }
}

class Circle {
    fun draw() {
        // draw circle
    }
}

위 코드는 개방-폐쇄 원칙을 위반합니다.

Rectangle 클래스와 Circle 클래스는 draw 메서드를 가지고 있습니다.

이를 개선하면 아래와 같습니다.

interface Shape {
    fun draw()
}

class Rectangle : Shape {
    override fun draw() {
        // draw rectangle
    }
}

class Circle : Shape {
    override fun draw() {
        // draw circle
    }
}

리스코프 치환 원칙 (Liskov Substitution Principle, LSP)

서브 타입은 언제나 기반 타입으로 대체할 수 있어야 한다.

open class Bird {
    open fun fly() {
        println("Bird is flying")
    }
}

class Ostrich : Bird() {
    override fun fly() {
        throw UnsupportedOperationException("Ostrich can't fly")
    }
}

위 코드는 리스코프 치환 원칙을 위반합니다.

Ostrich 클래스는 Bird 클래스를 상속받았지만, fly 메서드를 오버라이딩하여 Ostrich 클래스는 Bird 클래스의 서브 타입이 아닙니다.

이를 개선하면 아래와 같습니다.

open class Bird {
    open fun fly() {
        println("Bird is flying")
    }
}

class Ostrich : Bird() {
    override fun fly() {
        println("Ostrich can't fly")
    }
}

인터페이스 분리 원칙 (Interface Segregation Principle, ISP)

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다.

interface Worker {
    fun work()
    fun eat()
}

class Programmer : Worker {
    override fun work() {
        println("Programmer is working")
    }
    override fun eat() {
        println("Programmer is eating")
    }
}

class Waiter : Worker {
    override fun work() {
        println("Waiter is working")
    }
    override fun eat() {
        throw UnsupportedOperationException("Waiter can't eat")
    }
}

위 코드는 인터페이스 분리 원칙을 위반합니다.

Waiter 클래스는 eat 메서드를 구현하지만, Waiter 클래스는 eat 메서드를 사용하지 않습니다.

이를 개선하면 아래와 같습니다.

interface Workable {
    fun work()
}

interface Eatable {
    fun eat()
}

class Programmer : Workable, Eatable {
    override fun work() {
        println("Programmer is working")
    }
    override fun eat() {
        println("Programmer is eating")
    }
}

class Waiter : Workable {
    override fun work() {
        println("Waiter is working")
    }
}

의존 역전 원칙 (Dependency Inversion Principle, DIP)

고수준 모듈은 저수준 모듈에 의존해서는 안된다. 둘 다 추상화에 의존해야 한다. 또한, 추상화는 세부사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.

class Light {
    fun turnOn() {
        println("Light is on")
    }
    fun turnOff() {
        println("Light is off")
    }
}

class Switch {
    private val light = Light()
    fun on() {
        light.turnOn()
    }
    fun off() {
        light.turnOff()
    }
}

위 코드는 의존 역전 원칙을 위반합니다.

Switch 클래스는 Light 클래스에 의존하고 있습니다.

이를 개선하면 아래와 같습니다.

interface Switchable {
    fun turnOn()
    fun turnOff()
}

class Light : Switchable {
    override fun turnOn() {
        println("Light is on")
    }
    override fun turnOff() {
        println("Light is off")
    }
}

class Switch(private val switchable: Switchable) {
    fun on() {
        switchable.turnOn()
    }
    fun off() {
        switchable.turnOff()
    }
}