1. 전략 패턴(Strategy Pattern)
- 정책 패턴(Policy Pattern)이라고도 한다.
- 객체의 행위를 바꾸고 싶은 경우 '직접' 수정하지 않고 '전략'이라고 부르는 '캡슐화한 알고리즘'을 컨텍스트안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴.
- 즉, 어떤 기능을 수행하는 여러 가지 방법(전략)이 있을 때, 실행 시점에서 적절한 전략을 선택하여 사용할 수 있도록 설계하는 패턴.
2. Java의 전략 패턴 예시 코드
// 1. 전략(Strategy) 인터페이스
interface PaymentStrategy {
void pay(int amount);
}
// 2. 다양한 전략(결제 방법) 구현
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println(amount + "원 신용카드 결제 완료!");
}
}
class PayPalPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println(amount + "원 PayPal 결제 완료!");
}
}
// 3. 결제 컨텍스트(Context) 클래스
class PaymentProcessor {
private PaymentStrategy strategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void pay(int amount) {
if (strategy == null) {
System.out.println("결제 방식이 설정되지 않았습니다.");
return;
}
strategy.pay(amount);
}
}
// 4. 실행 코드 (Main)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
PaymentProcessor processor = new PaymentProcessor();
System.out.println("결제 방식을 선택하세요: 1. 신용카드 2. PayPal");
int choice = scanner.nextInt();
//if-else 문을 사용하여 사용자가 선택한 결제 방식을 설정
if (choice == 1) {
processor.setPaymentStrategy(new CreditCardPayment());
} else if (choice == 2) {
processor.setPaymentStrategy(new PayPalPayment());
} else {
System.out.println("잘못된 선택입니다.");
return;
}
processor.pay(5000);
}
}
코드 분석
1. PaymentStrategy 인터페이스
- 결제 방식을 정의하는 인터페이스
2. CreditCartPayment, PayPalPayment 클래스
- 결제 방식(전략)을 각각 구현
3. PaymentProcessor 컨텍스트
PaymentStrategy를 설정하고 실행하는 역할
4. Main 클래스
- 사용자가 결제 방식을 선택하고 실행하는 부분
실행 과정
Main 클래스 > 실행 시작
Scanner객체를 생성하여 사용자 입력 받기 준비 완료 !PaymentProcessor객체를 생성하여 결제 전략을 설정할 준비 완료 !- 사용자가 결제 방식을 선택하면
choice변수에 값 저장 if-else문을 사용하여 전략 설정
3. 전략 패턴의 장단점
장점
*OCP(개방-폐쇄 원칙, Open-Closed Principle): 기능을 확장할 수는 있지만, 기존 코드를 수정하지 않고 확장해야 한다.
- 실행 중 알고리즘(전략) 변경 가능 > 유연성 증가: 전략을 인터페이스로 추상화했기 때문에 실행중에 변경 가능하다.
- 코드 중복 제거 > 유지보수 용이: 각 알고리즘(전략)을 별도의 클래스로 분리하므로 중복된 코드가 사라진다.
- *OCP를 만족: 확장에는 열려 있고, 수정에는 닫혀 있음
- 결합도 감소 > 코드 유연성 증가: 클라이언트 코드가 특정 알고리즘(전략)과 강하게 결합되지 않음
- 테스트 및 코드 재사용 용이: 각 알고리즘(전략)이 독립적인 클래스로 분리되어 있어 단위 테스트가 쉬움, 공통 인터페이스를 사용하여 전략을 교체할 수 있음.
단점
- 클래스 수 증가 > 코드 복잡성 증가: 각 전략을 별도의 클래스로 분리해야 하므로 클래스 수가 증가, 작은 프로젝트에서는 오히려 불필요한 복잡성을 초래함.
- 클라이언트가 전략을 명시적으로 설정해야 함: 실수로 전략을 설정하지 않으면
NullPointerException발생 가능 !! - 전략 선택 로직이 분산될 수 있음: 클라이언트 코드에서 매번 전략을 선택해야 하므로 전략 선택 로직이 분산될 수 있음.
4. 전략 패턴을 사용해야 하는 경우
1. 여러 개의 알고리즘이 필요할 때
- 동일한 작업을 수행하는 여러 개의 알고리즘이 있을 때 사용
- 실행 중에 원하는 알고리즘을 선택하고 적용할 수 있어야 함 !
- ex) 정렬 알고리즘의 선택
2. 런타임에서 전략을 변경해야 할 때
- 프로그램 실행 도중 특정 조건에 따라 알고리즘을 변경해야 하는 경우
- ex) 결제 시스템(사용자가 결제 방식을 직접 선택하는 경우)
3. 코드 중복을 줄이고 유지보수성을 높이고 싶을 때
- 여러 개의 if문 또는 switch문을 제거하여 코드 중복을 줄이고 싶을 때
- 유지보수성을 높이고, 새로운 알고리즘(전략)이 추가될 때도 기존 코드 수정 없이 확장 가능
- 팩토리 패턴과 함께 사용 > 전략 객체를 생성하는 부분을 캡슐화 > if문 제거
- ex) 할인 시스템
4. 여러 객체가 같은 작업을 다른 방식으로 수행해야 할 때
- 여러 개의 객체가 같은 작업을 수행하지만, 방식이 다른 경우
- ex) 게임 캐릭터의 공격 방식(근접 공격 vs 원거리 공격)
5. 특정 기능이 계속 변경될 가능성이 클 때
- 특정 로직이 자주 변경될 것으로 예상될 때 미리 전략 패턴을 적용하면 유지보수성이 높아짐
- ex) AI 행동 패턴, 네트워크 요청 처리 방식, 로그 저장 방식
5. 전략 패턴과 팩토리 패턴을 함께 사용하기
// 1. 전략(Strategy) 인터페이스
interface PaymentStrategy {
void pay(int amount);
}
// 2. 다양한 전략(결제 방법) 구현
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println(amount + "원 신용카드 결제 완료!");
}
}
class PayPalPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println(amount + "원 PayPal 결제 완료!");
}
}
//*** 3. 팩토리 클래스 추가(전략을 생성 하는 역할)
class PaymentFactory{
public static PaymentStrategy getPaymentStrategy(String type){
switch(type.toUpperCase()){
case "CARD": return new CreditCardPayment();
case "PAYPAL": return new PayPalPayment();
default: throw new IllegalArgumentException("잘못된 결제 방식" + type);
}
}
}
// 4. 결제 컨텍스트(Context) 클래스
class PaymentProcessor {
private PaymentStrategy strategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void pay(int amount) {
if (strategy == null) {
System.out.println("결제 방식이 설정되지 않았습니다.");
return;
}
strategy.pay(amount);
}
}
// 4. 실행 코드 (Main)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
PaymentProcessor processor = new PaymentProcessor();
System.out.println("결제 방식을 입력하세요 (CARD / PAYPAL):");
String paymentType = scanner.next().toUpperCase();
//팩토리 패턴을 사용하여 if문 없이 전략 설정 ~!
processor.setPaymentStrategy(PaymentFactory.getPaymentStrategy(paymentType));
processor.pay(5000);
}
}
- 팩토리 패턴과 함께 사용하면
if-else문을 제거할 수 있다 !- 전략을 선택하는 로직을 별도의 클래스에 위임하면
if-else없이 깔끔하게 전략을 설정할 수 있음
- 전략을 선택하는 로직을 별도의 클래스에 위임하면
- 팩토리 패턴 없이도 해결 가능하지만 유지보수성이 떨어짐..
Map을 활용하여if문 없이 전략을 매핑할 수도 있지만, 새로운 전략을 추가할 때마다 직접Map을 수정해야 함..
6. 결론
전략 패턴은 알고리즘을 캡슐화하여 실행 중에도 유연하게 변경할 수 있도록 설계하는 패턴이다.
OCP(개방-폐쇄 원칙)을 준수하여, 기능을 확장할 때 기존 코드를 수정하지 않아도 된다.
여러 개의 알고리즘(전략)을 정의하고, 실행 시점에서 적절한 전략을 선택하여 사용할 수 있도록 한다.
유지보수성과 확장성을 고려한다면 전략 패턴과 팩토리 패턴을 함께 사용하는 것이 가장 좋다 !
'Computer Science > Design Pattern' 카테고리의 다른 글
| 이터레이터 패턴(Iterator Pattern) (0) | 2025.03.21 |
|---|---|
| 프록시 패턴(Proxy Pattern) (0) | 2025.03.19 |
| 옵저버 패턴(Observer Pattern) (1) | 2025.03.19 |
| 팩토리 패턴(Factory Pattern) (0) | 2025.03.17 |
| 싱글톤 패턴(Singleton Pattern) (1) | 2025.03.14 |
