혹시 코드에서 이런 장면, 낯설지 않으신가요?
개발을 하다 보면 특정 조건에 따라 다른 행동을 하도록 만드는 코드를 자주 작성하게 됩니다. 예를 들어, 게임 캐릭터가 사용하는 무기에 따라 공격 방식이 달라지거나, 쇼핑몰에서 사용자의 등급에 따라 할인율을 다르게 적용하는 경우를 떠올려 볼 수 있죠. 보통 이런 요구사항을 마주하면 우리는 자연스럽게 if-else나 switch 문을 사용하게 됩니다. 처음에는 몇 가지 조건만 처리하면 되니 간단하고 명확해 보입니다. 하지만 비즈니스 로직이 확장되면서 새로운 무기가 추가되고, 새로운 회원 등급이 생겨날 때마다 코드는 어떻게 변할까요?
맞습니다. if-else 블록은 끝없이 길어지고, 하나의 파일을 수정하기 위해 연관된 수많은 코드를 확인해야 하는 '지옥'이 펼쳐집니다. 새로운 기능을 하나 추가했을 뿐인데 예상치 못한 곳에서 버그가 터져 나오는 경험, 다들 한 번쯤은 있으실 겁니다. 바로 이런 문제를 해결하기 위해 등장한 멋진 해결사가 있습니다. 그것이 바로 전략 패턴(Strategy Pattern)입니다. 전략 패턴은 마치 레고 블록처럼, 행동(알고리즘)을 각각의 부품으로 만들어 놓고 필요할 때마다 갈아 끼울 수 있도록 설계하는 방식입니다. 덕분에 우리는 더 유연하고, 확장 가능하며, 유지보수하기 쉬운 코드를 만들 수 있게 됩니다.
| 패턴 | 주요 특징 | 장점 | 단점 | | :--- | :--- | :--- | :--- | | **if-else / switch** | 절차적 프로그래밍의 기본적인 조건 분기 구조 | 구현이 간단하고 직관적이며, 조건이 적을 때 가독성이 높음 | 조건이 많아질수록 코드가 복잡해지고, 새로운 조건 추가 시 기존 코드 수정이 불가피함(OCP 위반) | | **전략 패턴** | 알고리즘을 객체로 캡슐화하여 동적으로 교체 가능하도록 설계 | 개방-폐쇄 원칙(OCP)을 준수하여 확장성이 높고, 알고리즘별로 독립적인 테스트가 용이함 | 간단한 로직에 적용 시 클래스 수가 불필요하게 증가하여 과한 설계(Over-engineering)가 될 수 있음 | | **상태 패턴** | 객체의 내부 상태에 따라 행위를 변경하도록 설계 | 상태별 행위를 명확하게 분리할 수 있고, 복잡한 상태 전환 로직을 단순화함 | 상태와 행위가 밀접하게 연관된 경우에만 적합하며, 전략 패턴과 혼동하여 오용할 수 있음 | (출처: 소프트웨어 디자인 패턴 연구 보고서)그래서 전략 패턴이 정확히 뭔가요? 핵심 개념 파헤치기
전략 패턴의 핵심 아이디어는 아주 간단합니다. '알고리즘을 캡슐화하여 클라이언트로부터 분리하고, 동적으로 교체할 수 있도록 만드는 것'이죠. 말이 조금 어렵게 들릴 수 있지만, 세 가지 핵심 구성 요소를 살펴보면 금방 이해할 수 있습니다.
- 전략 (Strategy): 이것은 모든 구체적인 전략들이 따라야 하는 일종의 '규칙' 또는 '명세서'입니다. 보통 인터페이스(Interface)나 추상 클래스(Abstract Class)로 정의됩니다. 예를 들어 '할인한다'라는 기능의 인터페이스를 만들고, 그 안에는 'calculateDiscount'라는 메소드가 반드시 포함되어야 한다는 규칙을 정하는 것이죠.
- 구체적인 전략 (Concrete Strategy): '전략'이라는 규칙을 실제로 구현한 클래스들입니다. '정률 할인 전략', '정액 할인 전략', '신규 가입자 할인 전략'처럼 각각의 구체적인 할인 방식을 코드로 구현한 클래스들이 여기에 해당합니다. 각 클래스는 자신만의 방식으로 'calculateDiscount' 메소드를 완성합니다.
- 컨텍스트 (Context): 구체적인 전략을 사용하는 주체입니다. 컨텍스트는 어떤 전략을 사용할지에 대한 정보만 가지고 있으며, 그 구체적인 실행 내용은 알지 못합니다. 할인 정책을 적용하는 '주문(Order)' 객체가 컨텍스트에 해당할 수 있습니다.
자주 묻는 질문 (FAQ)
Q1. 전략 패턴은 언제 사용하는 것이 가장 좋은가요?
A. 전략 패턴은 하나의 작업을 처리하는 다양한 방식(알고리즘)이 존재하고, 런타임에 이 방식들을 동적으로 교체해야 할 필요가 있을 때 가장 유용합니다. 예를 들어, 파일 압축 시 ZIP, GZIP, BZIP2 등 다양한 압축 알고리즘을 선택적으로 사용하거나, 결제 시 신용카드, 계좌이체, 간편결제 등 여러 결제 수단을 처리해야 할 때 효과적입니다. 또한, if-else나 switch 문이 너무 길고 복잡해져 유지보수가 어려워질 때 리팩토링 대상으로 고려하기 좋습니다.
Q2. 전략 패턴과 상태 패턴(State Pattern)의 차이점은 무엇인가요?
A. 두 패턴 모두 객체의 행위를 변경한다는 점에서 유사하지만, 그 '의도'가 다릅니다. 전략 패턴은 클라이언트가 여러 알고리즘 중 하나를 선택하여 사용하는 것에 초점을 맞춥니다. 즉, '어떻게' 할 것인가에 대한 해답입니다. 반면, 상태 패턴은 객체의 내부 '상태'가 변함에 따라 행위가 자동으로 바뀌는 것에 중점을 둡니다. 상태 전환이 객체 내부에서 일어나며, '무엇'을 할 것인가가 상태에 따라 결정됩니다.
Q3. 전략이 몇 개 없을 때도 전략 패턴을 사용하는 것이 과한 설계(Over-engineering)가 아닐까요?
A. 좋은 지적입니다. 만약 2~3개의 간단하고 앞으로 변경될 가능성이 거의 없는 고정된 알고리즘만 존재한다면, 굳이 전략 패턴을 적용하는 것이 오히려 코드를 복잡하게 만들 수 있습니다. 이런 경우에는 간단한 if-else 문이 더 명확하고 효율적일 수 있습니다. 하지만, 향후 새로운 알고리즘이 추가될 가능성이 높거나, 각 알고리즘의 복잡도가 높은 경우에는 초기에 전략 패턴을 도입하는 것이 장기적인 유지보수 관점에서 훨씬 유리합니다.
Q4. 람다(Lambda)나 함수형 프로그래밍을 사용하면 전략 패턴을 더 간단하게 구현할 수 있나요?
A. 네, 그렇습니다. Java 8+, Python, JavaScript와 같은 현대적인 언어에서는 람다 표현식이나 고차 함수를 사용하여 전략 패턴을 훨씬 간결하게 구현할 수 있습니다. 각 전략을 별도의 클래스로 만들 필요 없이, 함수(또는 람다) 자체를 객체처럼 전달하여 컨텍스트에서 실행할 수 있습니다. 이는 코드의 양을 줄여주고 가독성을 높이는 데 큰 도움이 됩니다.
Q5. 국내 전자상거래법상 할인율 표기 시 주의할 점이 있나요?
A. 네, 매우 중요한 부분입니다. 전략 패턴을 이용해 다양한 할인 정책을 구현할 때, 최종적으로 사용자에게 보여지는 할인 정보는 「표시·광고의 공정화에 관한 법률」 및 「전자상거래 등에서의 소비자보호에 관한 법률」을 준수해야 합니다. 예를 들어, 할인 전 가격을 명확히 명시하고, 할인율 산정의 기준이 되는 가격을 허위로 부풀리지 않아야 합니다. 또한, '오늘만 이 가격'과 같은 시간 제한적 할인을 적용할 때는 그 기간을 명확히 고지해야 소비자에게 혼란을 주지 않습니다.

