티스토리 뷰
목차
1. OCP란?
OCP (Open/Closed Principle)
"소프트웨어 구성 요소는 확장에는 열려(Open)있고, 변경에는 닫혀(Closed) 있어야 한다."
- 기능이 확장될 수 있어야 하고
- 기존의 코드는 변경하지 않아야 한다는 객체지향 설계 원칙이다.
📌 즉, 새로운 요구사항이 생겨도 기존 코드를 수정하지 않고, 기능 추가만으로 시스템을 확장할 수 있어야 한다는 뜻이다.
2. 왜 OCP가 중요한가?
✅ OCP를 지키면
- 코드 변경 없이 새로운 기능을 추가할 수 있어 유지보수에 강함
- 기존 코드를 건드리지 않으므로 사이드 이펙트가 줄고 테스트가 쉬움
- 기능 추가가 많고, 요구사항이 자주 바뀌는 비즈니스 영역에서 매우 중요
❌ OCP를 지키지 않으면
- 기존 클래스에 계속 조건문이나 분기 로직이 추가됨 (if/else 지옥)
- 기능이 하나 추가될 때마다 전체 코드가 깨질 위험
- 테스트 범위가 넓어지고, 디버깅이 어려워짐
❌ 3. OCP 위반 예시
public class DiscountService {
public int calculateDiscount(String grade, int price) {
if (grade.equals("VIP")) {
return price * 20 / 100;
} else if (grade.equals("GOLD")) {
return price * 10 / 100;
} else {
return 0;
}
}
}
▶ 문제점
- 새로운 등급이 추가될 때마다 if를 추가해야 함 -> OCP 위반
- 로직이 등급별로 변경될수록 코드가 비대해짐
- 테스트 코드도 변경 필요 -> 유지보수 어려움
✅ 4. OCP를 만족하는 구조로 리팩토링
📌 전략패턴 + 다형성 기반으로 변경
- 공통 인터페이스 추출
public interface DiscountPolicy {
int discount(int price);
}
- 구현 클래스 분리
public class VipDiscountPolicy implements DiscountPolicy {
public int discount(int price) {
return price * 20 / 100;
}
}
public class GoldDiscountPolicy implements DiscountPolicy {
public int discount(int price) {
return price * 10 / 100;
}
}
- 서비스에서 인터페이스만 의존
public class DiscountService {
private final DiscountPolicy discountPolicy;
public DiscountService(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
public int applyDiscount(int price) {
return discountPolicy.discount(price);
}
}
➡ 등급별 할인 정책을 새로 추가하더라도 DiscountService는 절대 변경하지 않아도 됨 -> OCP 만족 ✅
5. 실무에서 자주 보는 OCP 위반 사례
| 위반 예 | 설명 |
| Controller에 등급별 조건문 분기 | 등급 추가 시 코드 변경 발생 |
| switch-case로 이벤트 처리 | 새로운 이벤트가 생기면 case 추가 필요 |
| 다양한 API 타입별 요청 처리 | 조건문 없이 각기 다른 구현체 주입 필요 |
6. Spring에서 OCP가 적용되는 구조
- Bean 등록 시 인터페이스 기반 주입
- @ComponentScan + @Qualifier 또는 @Primary 사용
- @Autowired Map<String, Bean> 구조로 다형성 분기 처리
@Component
public class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
public DiscountService(Map<String, DiscountPolicy> policyMap) {
this.policyMap = policyMap;
}
public int apply(String type, int price) {
DiscountPolicy policy = policyMap.get(type);
return policy.discount(price);
}
}
구현체는 늘어나도 service 코드는 변경되지 않음
Spring의 DI + 다형성을 활용한 OCP 실전 적용 예
7. OCP와 연관된 디자인 패턴
| 패턴 | 설명 |
| 전략 패턴 (Strategy Pattern) | 행위를 캡슐화하여 동적으로 교체 가능 |
| 의존성 주입 (Dependency Injection) | 구현체를 외부에서 주입받아 유연하게 확장 |
| 템플릿 메서드 패턴 | 공통 알고리즘 구조를 상위 클래스에 정의하고, 세부는 하위 클래스가 담당 |
✅ 마무리 요약
- OCP는 "기존 코드를 건드리지 않고, 새로운 기능을 확장할 수 있게 하자"는 원칙
- 조건문보다 다형성으로 확장 구조를 설계해야 한다.
- Spring의 DI, 전략 패턴을 통해 자연스럽게 OCP를 지킬 수 있다.
- 실무에서는 OCP 위반 여부가 테스트 난이도, 유지보수성, 재사용성에 직결된다.
🔜 다음 포스팅에서는 LSP-리스코프 치환 원칙 : "부모를 대체해도 문제가 없어야 한다" 로 다뤄보려고 한다.
'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 |
| SRP - 단일 책임 원칙 (Single Responsibility Principle) (0) | 2025.04.21 |
| SOLID 원칙 (2) | 2025.03.11 |