2026.01.28 (수)
SOLID 원칙이란/

클래스가 너무 많은 일을 하면 버그가 발생했을 때 수정하기 어렵고, 코드의 가독성이 떨어지기 때문에 하나의 클래스에 하나의 책임만 부여한다
1. SRP (단일 책임) "요리사는 요리만, 라이더는 배달만"
직원이라는 클래스가 요리도 하고, 배달도 하고, 결제도 다 하는 상황에
배달 방식이 오토바이에서 자전거로 바뀌었는데, 갑자기 요리하는 코드까지 건드려야 하는 상황이 생겼을 경우,해결: 요리사 클래스, 라이더 클래스, 결제원 클래스로 각각 나누자. 자기 할 일만 잘하면 된다!
2. OCP (개방-폐쇄) "메뉴 추가는 자유롭게, 주문 시스템은 그대로"
새로운 메뉴로 '마라탕'이 추가가 되었는데, 메뉴가 추가될 때마다 주문 앱의 전체 코드를 다시 짜야 한다면..?
해결: 메뉴라는 큰 틀(인터페이스)을 만들다. 치킨이든 마라탕이든 메뉴라는 틀만 지키면 주문 시스템은 수정 없이 그대로 새 메뉴를 받아들일 수 있다..!
3. LSP (리스코프 치환) "어떤 탈것이든 배달은 가능해야 한다"
배달 수단이라는 부모가 있고, 자식으로 오토바이와 자전거가 있을 경우,
"오토바이는 되는데 자전거는 배달을 못 해!"라고 프로그램이 멈춰버리면 안 된다.해결: 부모인 배달 수단 자리에 자전거를 넣어도 배달 로직은 정상적으로 '이동'하고 '도착'해야 한다.
4. ISP (인터페이스 분리) "손님 화면에 '주문 수락' 버튼을 넣지 마라"
배달 앱 인터페이스 하나에 '주문하기', '리뷰쓰기', '주문 수락하기', '음식 완료 알림' 버튼을 다 넣었을 경우, 손님은 '주문 수락' 기능을 전혀 안 쓰는데 이 버튼 때문에 화면이 복잡해졌다. 이럴 때 해결은?
해결: 손님용 인터페이스(주문, 리뷰)와 사장님용 인터페이스(수락, 알림)를 따로 분리해보자
5. DIP (의존성 역전) "결제 방식은 언제든 갈아 끼울 수 있어야 한다"
앱이 '네이버페이' 결제 시스템에 직접 납땜(직접 연결)되어 있었을 경우, 갑자기 '카카오페이'로 바꿔야 하면 앱을 거의 새로 만들어야 하는 상황이 생긴다. 이럴 때?
해결: 중간에 결제 시스템이라는 플러그(어댑터)를 둡니다. 앱은 플러그에만 연결하고, 거기에 네이버페이를 꽂든 카카오페이를 꽂든 앱은 상관없게 만드는 것 이다!
클린 아키텍처는 이 SOLID 원칙들을 층층이 쌓아 올린 것과 같다.
가장 중요한 것은 비즈니스 로직(배달 주문)을 가운데 보호하는 것이 핵심!!
// 1. 추상화 (DIP: 플러그 만들기)
// 어떤 결제 수단이든 이 규칙을 따라야 함
abstract class PaymentGateway {
void pay(int amount);
}
// 2. 저수준 모듈 (실제 구현체: 플러그에 꽂을 제품들)
class KakaoPay implements PaymentGateway {
@override
void pay(int amount) => print("카카오페이로 $amount원 결제");
}
class TossPay implements PaymentGateway {
@override
void pay(int amount) => print("토스페이로 $amount원 결제");
}
// 3. 고수준 모듈 (Clean Architecture의 UseCase: 배달 주문 로직)
// SRP: 주문하는 일만 책임짐
class OrderUseCase {
final PaymentGateway paymentMethod; // DIP: 구체적인 페이가 아닌 '플러그'에 의존
OrderUseCase(this.paymentMethod);
void completeOrder(int price) {
// 주문 완료 로직...
paymentMethod.pay(price);
}
}
변경에 강함
내일 당장 사장님이 "우리 이제 토스페이만 쓰자!"라고 해도 OrderUseCase 코드는 한 줄도 고칠 필요가 없다. (OCP)
테스트하기 좋음
실제 결제를 안 일으키고 가짜 결제(Mock) 플러그를 꽂아서 주문 로직이 잘 작동하는지 테스트할 수 있다.