S - Single Responsibility Principle (SRP)
O - Open/Closed Principle (OCP)
L - Liskov Substitution Principle (LSP)
I - Interface Segregation Principle (ISP)
D - Dependency Inversion Principle (DIP)
클래스는 하나의 책임만 가져야 한다. (변경 이유는 하나여야 한다)
예:
public class UserService {
public void registerUser(User user) {
// 회원 등록
}
public void sendWelcomeEmail(User user) {
// 이메일 발송 ❌ (책임이 다름)
}
}
개선:
public class UserService {
public void registerUser(User user) {
// 등록
}
}
public class MailService {
public void sendWelcomeEmail(User user) {
// 메일 전송
}
}
유지보수, 테스트, 책임 분리가 쉬워짐
확장에는 열려 있고, 변경에는 닫혀 있어야 한다
예: 결제 방식 추가하려면 기존 코드 뜯어야 함 ❌
public class PaymentService {
public void pay(String type) {
if (type.equals("kakao")) { ... }
else if (type.equals("payco")) { ... }
}
}
개선: 인터페이스 기반 설계
public interface PaymentStrategy {
void pay();
}
public class KakaoPay implements PaymentStrategy { ... }
public class Payco implements PaymentStrategy { ... }
public class PaymentService {
private final PaymentStrategy strategy;
public PaymentService(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePay() {
strategy.pay();
}
}
기능 추가는 클래스만 추가하면 되고, 기존 코드는 안 건드림
서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다
예: 부모 타입을 사용하는 코드가 자식 클래스에서 깨지면 ❌
public class Bird {
public void fly() { ... }
}
public class Ostrich extends Bird {
public void fly() {
throw new UnsupportedOperationException(); // 날지 못함 ❌
}
}
해결: 인터페이스나 계층 분리를 통해 fly()가 필요한 클래스들만 따로 관리
클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다
예:
public interface Worker {
void work();
void eat();
}
모든 클래스가 eat()을 구현해야 할까? ❌
개선:
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
필요한 기능만 구현하면 됨. 코드가 더 유연해짐
고수준 모듈은 저수준 모듈에 의존하면 안 된다. 추상화에 의존해야 한다.
예: 직접 구현체에 의존 ❌
public class OrderService {
private final MySQLRepository repo = new MySQLRepository();
}
개선: 인터페이스 도입 + 주입
public class OrderService {
private final Repository repo;
public OrderService(Repository repo) {
this.repo = repo;
}
}
유연한 테스트, 구조 확장 가능
| 원칙 | 요약 문구 | 목적 |
|---|---|---|
| SRP | 클래스는 하나의 책임만 | 변경 이유 명확하게 |
| OCP | 확장에는 열려, 변경에는 닫혀 | 기능 추가를 유연하게 |
| LSP | 부모로 대체 가능해야 함 | 타입 안전성 |
| ISP | 인터페이스는 작게 나눠라 | 불필요 의존 제거 |
| DIP | 구체화가 아닌 추상화에 의존 | 유연한 구조 설계 |