객체 지향 프로그래밍과 설계의 다섯가지 원칙을 첫 글자로 소개한 것
로버트 마틴이 정한 원칙을 마이클 페더스가 SOLID라는 이름으로 소개했다!
SOLID 원칙을 적용하게 되면 시간이 지나도 유지보수와 확장이 쉬운 시스템을 만들 수 있다.
단일 책임 원칙(Single Responsibility Principle)
클래스는 하나의 책임만 가진다.
여기서 책임이란 변경하려는 이유를 의미한다.
class Book {
func getTitle() -> String {
return "책 제목"
}
func printPage() {
// 현재 페이지 인쇄하기
}
}
Book 이라는 클래스에 책의 저자와 제목을 알 수 있는 메서드와 현재 페이지를 인쇄하는 메서드가 동시에 있다고 생각해보자. Book 객체를 사용하는 주체는 책을 관리하는 입장
과 데이터를 표현하는 입장
두 가지 유형으로 나눌 수 있다. 그러면 두 가지 역할이 혼합되어 단일 책임 규칙을 위배하게 되는 것이다.
class Book {
func getTitle() -> String {
return "책 제목"
}
}
class Print {
func printPage() {
// 현재 페이지 인쇄하기
}
}
위와 같은 경우에는 책을 관리하는 기능과 인쇄하는 기능을 분리해서 클래스로 만들면 된다.
개방폐쇄의 원칙(Open Close Principle)
소프트웨어의 요소는 확장에는 열려 있고, 변경에는 닫혀 있어야 한다.
class Area {
func calculateArea() -> Int {
if 도형 == 직사각형 {
//직사각형의 넓이 구하기
} else if 도형 == 원 {
//원의 넓이 구하기
}
}
}
도형의 넓이를 구하는 클래스 Area
에 calculateArea()
메서드가 있다면 직사각형의 넓이를 구하다가 원의 넓이도 구해야하면 직접 함수에 원일 경우를 추가해야 한다. 이는 수정에 닫혀 있지 않은 경우로 만약 삼각형의 넓이가 필요하다면 또 수정이 필요하다.
protocol Shape {
func calculateArea() -> Int
}
class Rectangle: Shape {
func calculateArea() -> Int {
//직사각형의 넓이 구하기
}
}
class Circle: Shape {
func calculateArea() -> Int {
//원의 넓이 구하기
}
}
OCP를 준수하기 위해서는 Shape
이라는 프로토콜에서 calculateArea()
메서드를 정의한 후 도형 별로 프로토콜을 채택하여 각자의 넓이를 구하도록 구현하면 된다.
리스코프 치환 원칙(Liskov substitution principle)
서브 타입은 언제나 기반 타입으로 호환될 수 있어야 한다.
치환이 된다는 말은 S가 T의 하위형이라면 별도의 변경 없이 T의 자료형을 S의 객체로 교체할 수 있다는 의미이다.
파생된 클래스는 베이스의 클래스의 기능을 교체하는 것이 아니라 유지하면서 단순히 확장해야 한다.
class Rectangle {
var width: Double = 0
var lenght: Double = 0
var area: Double {
return width * lenght
}
}
class Square: Rectangle {
override var width: Double {
didSet {
lenght = width
}
}
}
Rectangle
과 Square
가 정의되어 있다고 생각해보자.
func printArea(shape: Rectangle) {
shape.lenght = 5
shape.width = 2
print(shape.area)
}
let rectangle = Rectangle()
let square = Square()
printArea(shape: rectangle) // 10
printArea(shape: square) // 4
Square
가 Rectangle
를 상속했을 경우 Rectangle
의 방식으로 Square
의 넓이를 구하면 맞지 않는다. 즉 상위 타입이 하위 타입을 대체하지 못한 것으로 LSP를 위반한 경우다.
protocol Shape {
var area: Double { get }
}
class Rectangle: Shape {
var width: Double = 0
var lenght: Double = 0
var area: Double {
return width * lenght
}
}
class Square: Shape {
var side: Double = 0
var area: Double {
return side * side
}
}
func printArea(shape: Shape) {
print(shape.area)
}
이 경우에도 역시 프로토콜을 이용하면 된다. Square
가 Rectangle
모두 Shape
프로토콜을 채택한 후 area
를 구현하면 Shape
을 이용해 각각의 넓이를 맞게 구할 수 있다.
인터페이스 분리 원칙(Interface segregation principle)
가능한 최소한의 인터페이스만 사용해야 한다.
하나의 일반적인 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다는 의미로도 해석할 수 있다. SRP가 클래스의 단일책임을 강조한다면 ISP는 인터페이스의 단일책임을 강조한다.
클래스의 상속을 이용하여 인터페이스를 나눌 수 있다.
위임(Delegation)응 이용하여 인터페이스를 나눌 수 있다.
: 다른 클래스의 기능을 사용해야 하지만 그 기능을 변경하고 싶지 않을 때 상속 대신 위임을 사용
서로 다른 성격의 인터페이스를 명백히 분리한다.
의존관계 역전 원칙(Dependency inversion principle)
추상화에 의존해야지, 구체화에 의존하면 안된다.
의존성 주입이 이 원칙을 따르는 방법 중 하나이다. 하위 레벨 변경이 상위 레벨의 변경을 요구하지 않는다.