티스토리 뷰

IT/SOLID

OCP - 개방/폐쇄 원칙 (Open/Closed Principle)

Stv 2025. 4. 24. 15:46

목차


    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-리스코프 치환 원칙 : "부모를 대체해도 문제가 없어야 한다" 로 다뤄보려고 한다.