패턴의 토대가 되는 다섯 가지 설계 원칙.
S 단일 책임 (SRP) 클래스는 변경 이유가 하나여야 한다
O 개방-폐쇄 (OCP) 확장에는 열려 있고 수정에는 닫혀 있어야 한다
L 리스코프 치환(LSP) 하위 타입은 상위 타입을 대체할 수 있어야 한다
I 인터페이스 분리(ISP) 쓰지 않는 메서드에 의존하지 않게 잘게 나눈다
D 의존 역전 (DIP) 구체가 아니라 추상에 의존한다
SRP — "변경 이유"가 기준이다
// ❌ 이 클래스는 세 가지 이유로 바뀐다
class Report {
String generate() { ... } // 내용 규칙이 바뀌면
void saveToFile() { ... } // 저장 방식이 바뀌면
void sendEmail() { ... } // 발송 방식이 바뀌면
}
"하나만 한다"가 아니라 "바뀔 이유가 하나다" 가 정확한 기준이다.
OCP — 새 기능을 추가할 때 기존 코드를 안 고친다
// ❌ 결제 수단이 늘 때마다 이 메서드를 고쳐야 한다
void pay(String type) {
if (type.equals("card")) { ... }
else if (type.equals("bank")) { ... }
}
// ✅ 인터페이스를 두면 새 구현을 '추가' 하기만 하면 된다
interface PayMethod { void pay(long amount); }
void pay(PayMethod m, long amount) { m.pay(amount); }
Strategy 패턴이 곧 OCP의 구현이다. 대부분의 GoF 패턴이 이 원칙을 다른 상황에 적용한 것이다.
DIP — 화살표를 뒤집는다
❌ 서비스 → MySqlRepository (고수준이 저수준에 의존) ✅ 서비스 → Repository(인터페이스) ← MySqlRepository
- (저수준이 추상에 의존)
인터페이스를 '고수준 쪽' 이 소유하는 것이 핵심이다
GoF의 두 가지 대원칙
- ① 구현이 아니라 인터페이스에 프로그래밍하라
- ② 상속보다 합성을 선호하라
상속은 컴파일 시점에 고정되고 캡슐화를 깬다(부모 구현에 의존) 합성은 런타임에 바꿔 끼울 수 있다
원칙은 목적이 아니다
- 원칙을 지키려고 인터페이스를 남발하면 구현이 하나뿐인 추상이 쌓인다
- 실제로 바뀌는 축을 찾아 그곳만 유연하게 두는 것이 요령이다
- "지금 바뀌지 않는 것" 을 추상화하면 순수한 비용이다 (YAGNI)
면접 함정
- ❌ "SRP는 클래스를 작게 만드는 것" → 크기가 아니라 변경 이유의 수다.
- ❌ "인터페이스를 많이 만들면 좋은 설계" → 구현이 하나뿐인 인터페이스는 대개 불필요한 간접층이다.
LSP를 어기는 전형적 예
class Rectangle {
void setWidth(int w) { this.w = w; }
void setHeight(int h) { this.h = h; }
int area() { return w * h; }
}
class Square extends Rectangle { // "정사각형은 직사각형이다" — 수학적으로는 맞다
void setWidth(int w) { this.w = this.h = w; } // 높이까지 바꿔야 한다
void setHeight(int h) { this.w = this.h = h; }
}
void test(Rectangle r) {
r.setWidth(5); r.setHeight(4);
assert r.area() == 20; // Square 를 넘기면 16 이 나온다 → 깨진다
}
"is-a 관계"만으로는 부족하다 — 하위 타입은 상위의 계약까지 지켜야 한다. 이 예가 상속을 남용하면 안 되는 이유의 교과서다.
ISP — 뚱뚱한 인터페이스를 쪼갠다
// ❌ 프린터만 만드는 사람도 팩스·스캔을 구현해야 한다
interface Machine { void print(); void fax(); void scan(); }
// ✅ 필요한 것만 조합한다
interface Printer { void print(); }
interface Scanner { void scan(); }
class SimplePrinter implements Printer { }
원칙 사이의 긴장
SRP 를 극단으로 밀면 클래스가 폭발한다 OCP 를 극단으로 밀면 추상이 폭발한다
원칙은 서로 긴장 관계에 있고, 균형점은 '무엇이 실제로 바뀌는가' 가 정한다 바뀌지 않는 축을 유연하게 만드는 것은 순수한 낭비다