전락 패턴
전략 패턴(Strategy Pattern)은 알고리즘을 인터페이스로 정의하고, 이를 구현한 여러 전략 클래스를 사용하여 알고리즘을 동적으로 교체할 수 있도록 한다. 알고리즘을 객체로 캡슐화하여, 알고리즘을 사용하는 객체(컨텍스트)와 알고리즘을 분리함으로써 유연성과 확장성을 제공한다. 예를 들어, 결제 시스템에서 신용카드, 페이팔 등 다양한 결제 방식을 전략 패턴으로 구현하면, 결제 방식이 변경되거나 추가될 때 클라이언트 코드의 수정 없이 전략만 교체하면 된다.
// 🔹 전략 인터페이스 정의
interface PaymentStrategy {
pay(amount: number): void;
}
// 🔹 구체적인 전략 클래스 1: 신용카드 결제
class CreditCardPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`💳 신용카드로 ${amount}원 결제`);
}
}
// 🔹 구체적인 전략 클래스 2: 페이팔 결제
class PayPalPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`📧 PayPal로 ${amount}원 결제`);
}
}
// 🔹 컨텍스트 (실제 결제 시스템)
class PaymentProcessor {
private strategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: PaymentStrategy) {
this.strategy = strategy; // 전략 변경 가능
}
processPayment(amount: number) {
this.strategy.pay(amount);
}
}
// ✅ 실행 코드
const paymentProcessor = new PaymentProcessor(new CreditCardPayment());
paymentProcessor.processPayment(10000); // 💳 신용카드로 10000원 결제
// 🔄 실행 중 전략 변경 가능!
paymentProcessor.setStrategy(new PayPalPayment());
paymentProcessor.processPayment(5000); // 📧 PayPal로 5000원 결제
전략 패턴은 또한 if-else나 switch 문을 대체하여 조건에 따라 다른 행동을 처리하는 유연하고 확장 가능한 구조를 제공한다. 알고리즘을 독립적인 전략 객체로 캡슐화함으로써, 각 조건에 맞는 알고리즘을 동적으로 교체할 수 있다. 이로 인해 코드가 간결해지고, 가독성이 향상되며, 새로운 기능을 추가하거나 변경할 때 기존 코드를 수정할 필요 없이 전략만 확장하면 된다. 전략 패턴은 시스템을 확장 가능하고 유지보수가 용이하게 만들어, 코드의 유연성과 효율성을 높여준다.
// 1. 전략 인터페이스 정의
interface DiscountStrategy {
calculateDiscount(price: number): number;
}
// 2. 구체적인 전략 클래스들
class StudentDiscount implements DiscountStrategy {
calculateDiscount(price: number): number {
return price * 0.1;
}
}
class SeniorDiscount implements DiscountStrategy {
calculateDiscount(price: number): number {
return price * 0.2;
}
}
class EmployeeDiscount implements DiscountStrategy {
calculateDiscount(price: number): number {
return price * 0.3;
}
}
// 3. 전략 팩토리
class DiscountStrategyFactory {
static getDiscountStrategy(type: string): DiscountStrategy {
switch (type) {
case 'student':
return new StudentDiscount();
case 'senior':
return new SeniorDiscount();
case 'employee':
return new EmployeeDiscount();
default:
throw new Error('Unknown discount type');
}
}
}
// 4. 컨텍스트: 할인 계산기
class DiscountCalculator {
private strategy: DiscountStrategy;
constructor(strategy: DiscountStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: DiscountStrategy) {
this.strategy = strategy;
}
calculateDiscount(price: number): number {
return this.strategy.calculateDiscount(price);
}
}
지난 포스트에서 살펴본 템플릿 메서드 패턴 역시 알고리즘과 관련되어있다. 하지만 템플릿 메서드 패턴은 알고리즘의 기본적인 구조는 부모 클래스에서 정의하고, 일부 세부 구현만 상속을 통해 하위 클래스에서 변경하도록 강제하는 것에 중점을 두고 있다. 반면, 전략 패턴은 다양한 알고리즘을 독립적인 전략 객체로 구현하고, 런타임에 알고리즘을 동적으로 교체할 수 있도록 한다. 따라서, 전략 패턴은 유연하게 알고리즘을 교체하고 싶을 때 유리하며, 템플릿 메서드 패턴은 알고리즘의 흐름을 고정하고 일부 구현만 하위 클래스에서 변경하고 싶을 때 사용한다.
물론 템플릿 메서드 패턴을 사용해 알고리즘의 기본 흐름을 고정하고, 세부적인 부분은 전략 패턴으로 동적으로 교체하는 방식으로 두 패턴을 함께 사용하여 상호 보완적인 구조를 설계할 수 있다. 이 방식은 알고리즘의 흐름을 일정하게 유지하면서도, 유연하게 다양한 행동을 처리할 수 있게 해준다. 예를 들어, 결제 시스템에서 결제의 기본 흐름(예: 결제 전 체크, 결제 처리, 결제 후 로깅)은 템플릿 메서드 패턴을 사용해 고정하고, 결제 방식(예: 신용카드, 페이팔 등)은 전략 패턴을 통해 동적으로 변경함으로써, 각기 다른 결제 방법에 대한 세부 구현을 전략 객체로 교체할 수 있다. 이로 인해 시스템의 확장성은 높아지고, 코드의 중복은 줄어든다.
// 🔹 전략 인터페이스 정의
interface PaymentStrategy {
pay(amount: number): void;
}
// 🔹 구체적인 전략 클래스 1: 신용카드 결제
class CreditCardPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`💳 신용카드로 ${amount}원 결제`);
}
}
// 🔹 구체적인 전략 클래스 2: 페이팔 결제
class PayPalPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`📧 PayPal로 ${amount}원 결제`);
}
}
// 🔹 컨텍스트 (결제 처리 시스템) - 템플릿 메서드 적용
abstract class PaymentProcessor {
// 템플릿 메서드
processPayment(amount: number) {
this.checkPayment(amount); // 결제 전 체크
this.pay(amount); // 결제 처리 (구체적 전략에 위임)
this.logPayment(amount); // 결제 후 로깅
}
// 결제 전 체크 (공통 로직)
private checkPayment(amount: number) {
if (amount <= 0) {
throw new Error("결제 금액은 0보다 커야 합니다.");
}
console.log(`💰 ${amount}원 결제를 시작합니다.`);
}
// 결제 후 로깅 (공통 로직)
private logPayment(amount: number) {
console.log(`✔️ ${amount}원 결제가 완료되었습니다.`);
}
// 결제 처리 (구체적인 전략에 위임)
protected abstract pay(amount: number): void;
}
// 🔹 템플릿 메서드를 사용하는 실제 결제 시스템
class ConcretePaymentProcessor extends PaymentProcessor {
private strategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) {
super();
this.strategy = strategy;
}
// 전략에 따라 결제 처리 방식 결정
protected pay(amount: number): void {
this.strategy.pay(amount);
}
setStrategy(strategy: PaymentStrategy) {
this.strategy = strategy;
}
}
// ✅ 실행 코드
const paymentProcessor = new ConcretePaymentProcessor(new CreditCardPayment());
paymentProcessor.processPayment(10000); // 💰 10000원 결제를 시작합니다. 💳 신용카드로 10000원 결제 ✔️ 10000원 결제가 완료되었습니다.
// 🔄 실행 중 전략 변경 가능!
paymentProcessor.setStrategy(new PayPalPayment());
paymentProcessor.processPayment(5000); // 💰 5000원 결제를 시작합니다. 📧 PayPal로 5000원 결제 ✔️ 5000원 결제가 완료되었습니다.
블로그의 정보
Ayden's journal
Beard Weard Ayden