티스토리 뷰
목차
SOLID 원칙은 유지보수성, 확장성, 유연성, 테스트 용이성을 갖춘 객체지향 설계를 위한 핵심 5대 원칙이다.
이 원칙을 제대로 이해하고 실무에 적용하면, 요구사항 변경에도 끄떡없는 탄탄한 시스템을 만들 수 있다.
1. SRP - 단일 책임 원칙 (Signle Responsibility Principle)
📌 정의
"하나의 클래스는 하나의 책임만 가져야 한다."
정확히 말하면 클래스는 오직 하나의 변경 이유만 가져야 한다.
🎯 왜 중요한가?
- 여러 책임을 가진 클래스는 변경 범위가 커짐
- 하나의 책임이 바뀔 때 전혀 관련 없는 기능까지 깨질 위험있음
- 테스트가 어려워지고 응집도가 낮아짐
❌ 위반 예시
public class ReportManager {
void generateReport() { ... }
void saveToDB() { ... }
void sendEmail() { ... }
}
➡️ 생성/저장/전송이라는 3가지 책임을 동시에 가짐
✅ SRP 적용 구조
class ReportGenerator { ... }
class ReportSaver { ... }
class ReportSender { ... }
🔎 실무 포인트
- "그리고(and)"가 클래스/메서드 이름에 들어가면 SRP 의심
- Controller가 로직 + 응답 포맷 + 데이터 호출까지 한다면 분리 고려
✅ 2. OCP - 개방/폐쇄 원칙 (Open/Close Principle)
📌 정의
"소프트웨어 구성 요소는 확장에는 열려 있고, 변경에는 닫혀 있어야 한다."
🎯 왜 중요한가?
- 기존 코드를 수정하지 않고도 새로운 기능을 추가할 수 있어야 함
- OCP 위반 -> 수정 시 Side Effect -> 테스트 대혼란
❌ 위반 예시
if (type == "A") { ... }
else if (type == "B") { ... }
else if (type == "C") { ... }
➡️ 새로운 타입이 생길 때마다 조건문 추가 -> 기존 코드 건드려야 함
✅ OCP 적용 구조 (전략 패턴 활용)
interface DiscountPolicy { ... }
class VipDiscountPolicy implements DiscountPolicy { ... }
class GoldDiscountPolicy implements DiscountPolicy { ... }
class OrderService {
private final DiscountPolicy policy;
public OrderService(DiscountPolicy policy) { this.policy = policy; }
}
🔎 실무 포인트
- if/else가 반복된다면 -> 다형성으로 분기 처리
- Spring에서는 인터페이스 + @Autowired 조합으로 OCP 실현 가능
✅3. LSP - 리스코프 치환 원칙 (Liskov Subsititution Principle)
📌정의
"자식 클래스는 부모 클래스를 대체해도 제대로 동작해야 한다."
🎯왜 중요한가?
- 다형성을 유지하려면 하위 타입이 상위 타입의 동작 계약을 깨면 안 됨
- 자식 클래스가 부모 클래스의 기능을 무력화하거나, 의도를 왜곡하면 설계 전체가 망가짐
❌ 위반 예시
class Rectangle {
void setWidth(int w) { ... }
void setHeight(int h) { ... }
}
class Square extends Rectangle {
void setWidth(int w) { setHeight(w); }
void setHeight(int h) { setWidth(h); }
}
➡️ Square는 Rectangle을 대체할 수 없는 잘못된 is-a 관계 -> LSP 위반
✅ LSP 적용 구조
- 공통 인터페이이스로 분리하고, 동작 방식이 다른 객체는 아예 분리
interface Shape { int area(); }
class Rectangle implements Shape { ... }
class Square implements Shape { ... }
🔎 실무 포인트
- 자식 클래스가 부모 클래스 메서드를 무력화하거나 예외를 던지면 LSP 의심
- 테스트 코드에서 다형성이 안 된다면 LSP 위반 가능성이 높음
✅ 4. ISP - 인터페이스 분리 원칙 (Interface Segregation Principle)
📌정의
"클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다."
🎯왜 중요한가?
- Fat Interface는 모든 구현체가 필요 없는 메서드까지 구현하게 만듦
- 불필요한 의존성과 변경 전파를 유발
- 모듈화/테스트성/유지보수성 모두 하락
❌위반 예시
interface Machine {
void print();
void scan();
void fax();
}
class Printer implements Machine {
public void print() { ... }
public void scan() { throw new UnsupportedOperationException(); }
public void fax() { throw new UnsupportedOperationException(); }
}
✅ ISP 적용 구조
interface Printer { void print(); }
interface Scanner { void scan(); }
interface Fax { void fax(); }
class MultiFunctionPrinter implements Printer, Scanner, Fax { ... }
class SimplePrinter implements Printer { ... }
🔎 실무 포인트
- 서비스 인터페이스가 너무 많다면 분리 대상
- 클라이언트(사용자)의 관점에서 "정말 필요한 기능"만 노출해야 함
✅ 5. DIP - 의존성 역전 원칙 (Dependency Inversion Principle)
📌정의
"상위 모듈은 하위 모듈에 의존하면 안 된다. 모두가 추상화에 의존해야 한다."
🎯왜 중요한가?
- 고수준 비즈니스 로직이 저수준 구현체에 의존하면 유연하지 못함
- 구현이 바뀔 때마다 비즈니스 로직이 수정됨 -> 유지보수 어려움
❌위반 예시
public class OrderService {
private final FileLogger logger = new FileLogger(); // 직접 의존
}
✅ DIP 적용 구조
interface Logger { void write(String msg); }
class FileLogger implements Logger { ... }
class OrderService {
private final Logger logger;
public OrderService(Logger logger) { this.logger = logger; }
}
➡️ OrderService는 구현이 아닌 Logger라는 추상화에만 의존
🔎 실무 포인트
- DIP를 위해 꼭 필요한 것 -> 인터페이스 기반 설계 + 의존성 주입(DI)
- Spring은 기본적으로 DIP가 실현된 구조 (Bean 주입, 인터페이스 기반 프로그래밍)
🔁 SOLID 전체 요약 도표
| 원칙 | 목적 | 지키면 | 안 지키면 |
| SRP | 단일 책임 | 클래스 하나 = 하나의 역할 | 변경 꼬이고, 테스트 어려움 |
| OCP | 확장에는 열림, 변경에는 닫힘 | 기존 코드 변경 없이 저장 | if/else 지옥, 사이드 이펙트 |
| LSP | 부모 -> 자식 치환 가능 | 다형성 유지, 유연한 설계 | 예상치 못한 동작, 다형성 파괴 |
| ISP | 인터페이스 분리 | 필요한 기능만 의존 | Fat Interface로 유지보수 어려움 |
| DIP | 추상화에 의존 | 유연하고 테스트 쉬움 | 구현체에 강결합, 확장 불가 |
🛠️ 실무 적용 전략
- 인터페이스 기반으로 설계하고 구현은 DI로 주입
- 서비스 클래스는 단일 책임을 가지도록 설계
- 전략 패턴 등으로 조건 분기 대신 다형성 활용
- 테스트 가능한 구조 = 좋은 설계의 핵심
- SOLID는 따로따로가 아닌, 서로 연결된 원칙들임
📚 마무리 : SOLID는 "읽는 것"이 아니라 "디자인에 녹여야" 하는 것
- SRP -> 변경 이유를 나누자
- OCP -> 코드는 그대로, 기능만 추가하자
- LSP -> 진짜 is-a 관계인지 확인하자
- ISP -> 작은 인터페이스가 더 유연하다
- DIP -> 구현이 아니라 역할에 집중하자
🔜 다음 포스팅부터는 Spring에 대해서 깊게 들어가보고자 한다. 먼저 IoC, DI, AOP에 대해서 깊게 들어가보려고 한다. 또한 SOLID와도 연관지어 내용을 다뤄보도록 하겠다.
'IT > SOLID' 카테고리의 다른 글
| DIP - 의존성 역전 원칙 (Dependency Inversion Principle) (0) | 2025.04.29 |
|---|---|
| ISP - 인터페이스 분리 원칙(Interface Segregation Principle) (0) | 2025.04.28 |
| LSP - 리스코프 치환 원칙 (Liskov Substitution Principle) : "부모를 대체해도 문제가 없어야 한다" (0) | 2025.04.25 |
| OCP - 개방/폐쇄 원칙 (Open/Closed Principle) (0) | 2025.04.24 |
| SRP - 단일 책임 원칙 (Single Responsibility Principle) (0) | 2025.04.21 |