SOLID 설계 원칙
- 5.1 SRP — 단일 책임 원칙
- 5.2 OCP — 개방-폐쇄 원칙
- 5.3 LSP — 리스코프 치환 원칙
- 5.4 ISP — 인터페이스 분리 원칙
- 5.5 DIP — 의존 역전 원칙
객체 지향 문법을 아는 것과 잘 쓰는 건 완전히 다른 문제야. SOLID는 그 문법을 어떻게 잘 쓸 것인가에 대한 답이지.
SRP(단일 책임 원칙) 부터 보자. "클래스를 변경해야 하는 이유는 하나뿐이어야 한다"는 건데, 여기서 "책임"은 "변경의 이유"야. Employee 클래스가 급여 계산, 근무 보고, DB 저장을 전부 하고 있으면, 세 가지 이유로 바뀔 수 있잖아. 급여 로직 고치다가 DB 저장이 깨질 수 있다는 뜻이야. "이 클래스가 뭘 하는가"보다 "이 클래스가 언제 바뀌는가"를 기준으로 쪼개야 해.
OCP(개방-폐쇄 원칙) 는 SOLID 중에서 가장 중요해. 확장에는 열려 있고 변경에는 닫혀 있어야 한다는 건데, 이걸 가능하게 하는 게 3장에서 배운 다형성이야. 엔진 종류마다 if-else로 분기하는 대신 Engine 인터페이스를 만들어놓으면, 수소 엔진이 추가돼도 HydrogenEngine implements Engine만 만들면 끝이야. 기존 Car 코드는 한 줄도 안 건드려.
LSP(리스코프 치환 원칙) 는 상속의 is-a 관계를 더 엄밀하게 검증하는 거야. 하위 타입이 상위 타입을 대체할 수 있어야 한다는 건데, 유명한 직사각형/정사각형 문제가 딱 이거지. 수학적으로 정사각형이 직사각형의 특수한 경우라고 해서 코드에서도 상속이 맞는 건 아니야. setWidth()할 때 높이까지 바뀌면 Rectangle로 쓸 때 기대하는 동작이 깨지거든. is-a가 행위적으로도 성립하는지 따져야 해.
ISP(인터페이스 분리 원칙) 는 SRP의 인터페이스 버전이야. SmartDevice 하나에 전화, 메시지, 웹, 카메라, 음악을 다 넣으면, 일반 전화기가 이걸 구현할 때 쓰지도 않는 메서드를 억지로 가져야 하잖아. Callable, Messageable 같이 잘게 쪼개면 각 구현체가 필요한 것만 골라 쓸 수 있어.
마지막 DIP(의존 역전 원칙) 가 스프링으로 직결돼. OrderService가 MySQLRepository라는 구체 클래스에 직접 의존하면, PostgreSQL로 바꿀 때 서비스 코드까지 수정해야 해. 대신 Repository 인터페이스에 의존하면 구현체가 뭐든 상관없지. 원래 고수준 모듈이 저수준 모듈을 직접 의존하던 방향이, 둘 다 추상화를 향하도록 뒤집힌 거야. 7장에서 스프링이 의존성을 외부에서 주입(DI)하는 이유가 바로 이 DIP를 구현하기 위해서야.
정리
5장 읽고 기억할 거 다섯 가지:
- SRP — 하나의 클래스는 하나의 변경 이유만 가져야 한다. "책임 = 변경의 이유"로 판단
- OCP — 확장에 열려 있고 변경에 닫혀 있어야 한다. 다형성으로 구현. 새 기능을 추가할 때 기존 코드를 건드리지 않는 게 목표
- LSP — 하위 타입은 상위 타입을 대체할 수 있어야 한다. 상속의 is-a 관계가 행위적으로도 성립하는지 검증
- ISP — 범용 인터페이스 하나보다 구체적 인터페이스 여러 개가 낫다. 클라이언트가 쓰지 않는 메서드에 의존하지 않도록
- DIP — 구체 클래스가 아닌 추상화에 의존하라. 이게 스프링 IoC/DI의 이론적 기반이다