SOLID는 객체 지향 프로그래밍과 설계에서 중요한 다섯가지 원칙을 나타내는 약어이다. 각 글자는 특정한 설계 원칙을 의미하고, 이 원칙들을 통해 더 유지보수가 쉽고, 확장 가능하며, 이해하기 쉬운 코드를 작성할 수 있도록 도와준다.
Single Responsibilty Principle
단일 책임 원칙으로써 클래스는 하나의 목적을 가져야하며, 클래스를 변경하는 이유는 단 하나의 이유여야 한다.
SRP를 위반하는 예와 준수하는 예를 한번 살펴보자
- SRP를 위반하는 예
class UserManager {
func createUser(name: String, age: Int) {
// 유저 생성 로직
}
func sendWelcomeEmail(email: String) {
// 환영 이메일 발송 로직
}
}
- SRP를 준수하는 예
class UserManager {
func createUser(name: String, age: Int) {
// 유저 생성 로직
}
}
class EmailService {
func sendWelcomeEmail(email: String) {
// 환영 이메일 발송 로직
}
}
위 예제에서 UserManger 클래스는 유저 생성에만 책임을 지고, 이메일 발송은 EmailService 클래스에서 담당하게 된다.
Open/Closed Principle
개방/폐쇄 원칙 소프트 웨어는 확장에는 열려 있어야 하지만 수정에는 닫혀있어야 한다. 즉 기존의 코드를 변경하지 않고도 객체의 기능을 확장할 수 있어야 한다.
- OCP 위반 예
class Discount {
func calculate(price: Double, discountType: String) -> Double {
if discountType == "percentage" {
return price * 0.9
} else if discountType == "fixed" {
return price - 10
}
return price
}
}
- OCP 준수 예
protocol DiscountStrategy {
func calculate(price: Double) -> Double
}
class PercentageDiscount: DiscountStrategy {
func calculate(price: Double) -> Double {
return price * 0.9
}
}
class FixedDiscount: DiscountStrategy {
func calculate(price: Double) -> Double {
return price - 10
}
}
class Discount {
private let strategy: DiscountStrategy
init(strategy: DiscountStrategy) {
self.strategy = strategy
}
func calculate(price: Double) -> Double {
return strategy.calculate(price)
}
}
Discount클래스가 DiscountStrategy 프로토콜을 통해 확장 가능하도록 설계 되어 있습니다.
Liskov Substitution Principle
상위 타입의 객체를 하위 타입으로 바꾸어도 프로그램은 일관되게 동작되어야 한다.
- LSP 위반 예
class Bird {
func fly() {
// 나는 로직
}
}
class Penguin: Bird {
override func fly() {
// 펭귄은 날 수 없기 때문에 예외를 던질 수 있다.
fatalError("Penguins can't fly!")
}
}
- LSP 준수 예
class Bird {
func move() {
// 이동 로직
}
}
class FlyingBird: Bird {
override func move() {
fly()
}
func fly() {
// 나는 로직
}
}
class Penguin: Bird {
override func move() {
// 수영 로직
}
}
Penguin 이 Bird 의 서브 클래스이지만, fly 메소드를 오버라이드 하지 않고 move 메소드를 통해 적절한 동작을 수행합니다.
Interface Segregation Principle
자신이 사용하지 않는 인터페이스를 구현하도록 강제되어서는 안된다. 클라이언트가 필요로 하는 메서드만 포함하도록 여러 개의 구체적인 인터페이스가 더 낫다.
- ISP 위반 예
protocol Worker {
func work()
func eat()
}
class HumanWorker: Worker {
func work() {
// 작업 로직
}
func eat() {
// 식사 로직
}
}
class RobotWorker: Worker {
func work() {
// 작업 로직
}
func eat() {
// 로봇은 먹을 수 없기 때문에 예외를 던질 수 있다.
fatalError("Robots don't eat!")
}
}
- ISP 준수 예
protocol Workable {
func work()
}
protocol Eatable {
func eat()
}
class HumanWorker: Workable, Eatable {
func work() {
// 작업 로직
}
func eat() {
// 식사 로직
}
}
class RobotWorker: Workable {
func work() {
// 작업 로직
}
}
Worker 인터페이스를 Workable과 Eatable 인터페이스로 분리하여 로봇이 사용하지 않는 메서드에 의존하지 않도록 한다.
Dependency Inversion Principle
클라이언트는 추상화에 의존해야 하며, 구체화(구현된 클래스)에 의존해선 안된다.
- DIP 위반 예
class Database {
func save(data: String) {
// 데이터 저장 로직
}
}
class DataManager {
private let database = Database()
func saveData(data: String) {
database.save(data: data)
}
}
- DIP 준수 예
protocol DataStore {
func save(data: String)
}
class Database: DataStore {
func save(data: String) {
// 데이터 저장 로직
}
}
class DataManager {
private let dataStore: DataStore
init(dataStore: DataStore) {
self.dataStore = dataStore
}
func saveData(data: String) {
dataStore.save(data: data)
}
}
DataManager가 Database 클래스에 직접 의존하지 않고, DataStore 프로토콜에 의존하도록 하여 의존성을 역전시킨다.
위 내용과 같이 SOLID 원칙을 따르도록 노력하면 코드가 더 유연해지고 유지보수성도 높아지며 확장성을 챙길 수 있게된다.
각 원칙은 구체적인 문제를 해결하는데 중점을 두고 있어 이를 통해 코드의 좋은 품질을 바랄 수 있다.
앞으로 해당 내용을 파악 하고 이유가 있는 좋은 코드를 만들어 나가고 싶다.
'◽️ Programming > ◽️ Computer Science' 카테고리의 다른 글
[CS] 컴퓨터 구조에 대해서 자세하게 알아보자 (1) | 2024.07.15 |
---|---|
[CS] 운영체제에 대해서 자세하게 알아보자 (0) | 2024.07.13 |
비트와 바이트 ( CS 50 강의 ) (1) | 2024.04.19 |
기억장치 ( CS 50 강의 ) (0) | 2024.04.08 |
하드웨어 (CS 50 강의) (0) | 2024.03.27 |