0. 나는 정말 알고 있었을까?
그동안 인터페이스를 사용하고, 공부하면서 SOLID 라는 단어는 많이 들었다.
강의를 들을 때는 이해한 것 같았지만, 막상 스스로에게 이렇게 질문해 보면 답이 막혔다.
“그래서 SOLID가 왜 필요한 건데?”
이 글은 그 질문에 답하기 위해 정리한 기록이다.
1. 내가 이해한 SOLID
1) SRP – 단일 책임 원칙 (Single Responsibility Principle)
처음에는 “클래스는 한 가지 일만 해야 한다”라고 이해했다.
지금 이해한 SRP는 이것이다.
클래스는 변경의 이유가 하나여야 한다.
예를 들어 MemberService가 회원 가입뿐 아니라 할인 정책 계산까지 담당한다고 가정해보자.
- 회원 정책이 바뀌어도 수정
- 할인 정책이 바뀌어도 수정
이 순간 클래스는 두 가지 이유로 변경된다.
할인 정책이 바뀌었는데 회원 로직이 들어있는 클래스를 수정해야한다면
그것이 바로 SRP 위반의 신호다.
SRP는 결국 "일을 하나만 하라"는 말이 아니라,
책임의 축을 분리하라는 설계 원칙이다.
2) OCP – 개방-폐쇄 원칙 (Open-Closed Principle)
확장에는 열려 있고, 변경에는 닫혀 있어야 한다.
처음에는 문장 자체가 추상적으로 느껴졌다.
하지만 실제 코드에 적용해 보니 의미가 분명해졌다.

예를 들어 할인 정책을 FixDiscountPolicy에서
RateDiscountPolicy로 변경해야 한다고 가정하자.
만약 OrderService 내부 코드를 직접 수정해야 한다면,
그 구조는 OCP를 지키지 못한 것이다.
새로운 기능을 추가할 때 기존 코드를 수정하지 않아도 되는 구조
진짜 OCP는 위와 같고, 이를 가능하게 만드는 도구가 바로 인터페이스와 다형성이다.
3) LSP – 리스코프 치환 원칙 (Liskov Substitution Principle)
부모 타입을 사용하는 코드에서 자식 타입으로 바꿔도 동작이 깨지면 안 된다.
단순히 “상속받았으니 대체 가능하다”는 의미가 아니다.
예를 들어 DiscountPolicy는 “할인 금액을 반환한다”는 계약을 가진다.
그런데 어떤 구현체가 음수 금액을 반환한다면?
컴파일은 되지만 행동 계약을 위반한 것이다.
LSP는 타입 상속의 문제가 아니라
행동의 일관성과 계약 준수의 문제다.
4) ISP – 인터페이스 분리 원칙 (Interface Segregation Principle)
클라이언트는 자신이 사용하지 않는 메서드에 의존하면 안 된다.
하나의 거대한 인터페이스가 여러 기능을 포함하고 있다면
어떤 구현체는 사용하지도 않는 메서드를 억지로 구현해야 한다.
이런 구조는 변경에 취약하다.
인터페이스를 역할별로 분리하면:
- 불필요한 의존이 제거되고
- 결합도가 낮아지며
- 변경 영향 범위가 줄어든다
ISP는 결국 결합도를 낮추는 전략이다.
5) DIP – 의존관계 역전 원칙 (Dependency Inversion Principle)
구체 클래스가 아니라 추상화에 의존하라.
예를 들어 다음과 같은 코드가 있다고 가정하자.
public class OrderService {
private DiscountPolicy discountPolicy = new FixDiscountPolicy();
}
이 구조에서는 OrderService가 구체 클래스 FixDiscountPolicy에 직접 의존한다.
할인 정책이 바뀌면 OrderService를 수정해야 한다.
OCP도 깨지고, DIP도 깨진다.
DIP를 지키려면 이렇게 되어야 한다.
public class OrderService {
private final DiscountPolicy discountPolicy;
public OrderService(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
이제 OrderService는 인터페이스에만 의존한다.
구현체는 외부에서 결정된다.
여기서 등장하는 개념이 바로 DI(Dependency Injection)다.
2. DI란 무엇인가?
DI (의존성 주입)은 말 그대로 의존 객체를 외부에서 주입받는 방식이다.
객체가 직접 의존 객체를 생성하지 않고, 외부(컨테이너)가 대신 생성하고 연결해준다.

DI가 필요한 이유
- 구현체 교체가 쉬워진다.
- 테스트가 쉬워진다.
- OCP, DIP를 자연스럽게 지킬 수 있다.
즉, DI는 단순한 편의 기능이 아니라
설계 원칙을 지키게 해주는 구조적 장치다.
3. 공부하며 느낀점
스프링이 왜 복잡한 구조를 가지는지 이제야 이해가 갔다.
- 인터페이스를 만들고
- 구현체를 분리하고
- 생성자로 주입받고
- 설정 클래스로 연결하고
이 모든 과정은 귀찮아 보인다.
하지만 그 귀찮음을 감수하면 변경 비용이 줄어드는 구조가 만들어진다.
스프링은 단순한 프레임워크가 아니라, 우리가 SOLID 원칙을 지키도록 판을 깔아주는 도구였다
4. 핵심 정리
- SRP: 변경 이유를 하나로 모은다.
- OCP: 기존 코드를 수정하지 않고 확장한다.
- LSP: 다형성이 깨지지 않도록 계약을 지킨다.
- ISP: 불필요한 의존을 제거한다.
- DIP: 구현이 아니라 추상에 의존한다.
- DI: DIP를 실천하기 위한 구현 전략이다.
결국 SOLID는 지금 당장을 위한 기술이 아니라,
미래의 나, 그리고 동료들이 마주할 변경의 고통을 미리 덜어내기 위한 최소한의 안전장치이다.