웹, 앱
[NestJS] Strategy Pattern (전략 패턴)
Hyun-danpung2
2024. 11. 13. 23:03
728x90
반응형
1. 서론
전략 패턴에 대해서는 간단하게 알고 있었지만 한동안은 if-else 문 혹은 switch 문과의 차이를 크게 못 느꼈었다. 그러다가 실무에서 어떤 환경이 주어졌을 때 전략 패턴이 떠올랐고 적용하면서 이점을 느끼게 되어 기록하게 되었다. 실무에서 NestJS를 사용 중이기에 제목과 예시 코드가 NestJS 이지만 디자인 패턴 중 하나인 만큼 어디에도 적용이 가능할 것이다.
2. 본론
- 정의
- Strategy Pattern (전략 패턴)은 객체의 행위를 정의하는 방법 중 하나
- 알고리즘을 정의하고 이를 캡슐화하여 클라이언트 코드에서 독립적으로 사용할 수 있도록 하는 디자인 패턴
- 여러 알고리즘을 정의하고, 그 알고리즘을 동적으로 선택하여 사용
- 주요 구성 요소
- Context: 전략을 사용하는 클라이언트 객체
- Strategy Interface: 다양한 알고리즘을 정의하는 인터페이스
- Concrete Strategies: Strategy 인터페이스를 구현하여 구체적인 알고리즘을 제공하는 클래스
- 사용 사례
- 다양한 알고리즘 필요
- 동일한 작업을 수행하지만 여러 알고리즘이 필요할 때 사용 됨
- 예를 들어, 정렬 알고리즘이 다르게 적용되거나 결제 처리 방식이 다르게 적용되는 경우 등
- 런타임에 알고리즘 변경
- 프로그램 실행 중에 사용자의 입력이나 환경에 따라 알고리즘을 변경해야할 때 유용하게 사용
- 예를 들어, 사용자에게 다양한 결제 수단을 제공하고 선택에 따라 다른 알고리즘이 적용되는 경우 등
- 복잡한 조건문 제거
- if-else 또는 switch-case가 여러번 사용되어 알고리즘을 구분하는 코드를 간소화할 때 사용
- 통일된 인터페이스 제공
- 서로 다른 알고리즘이 있지만 클라이언트가 알 필요 없는 상황에 하나의 인터페이스를 클라이언트에게 제공하고 내부적으로 구분할 때 사용
- 다양한 알고리즘 필요
- 예시: 결제 시 할인 로직
// 1. Strategy Interface 정의
export interface DiscountStrategy {
applyDiscount(price: number): number;
}
// 2. Concrete Strategies 정의
import { DiscountStrategy } from './discount-strategy.interface';
// 할인 없음
export class NoDiscountStrategy implements DiscountStrategy {
applyDiscount(price: number): number {
return price;
}
}
// 퍼센트 할인
export class PercentageDiscountStrategy implements DiscountStrategy {
constructor(private readonly percentage: number) {}
applyDiscount(price: number): number {
return price - (price * this.percentage) / 100; // 퍼센트 할인
}
}
// 고정 금액 할인
export class FixedAmountDiscountStrategy implements DiscountStrategy {
constructor(private readonly amount: number) {}
applyDiscount(price: number): number {
return price - this.amount;
}
}
// 3. service 로직
@Injectable()
export class PaymentService {
private strategy: DiscountStrategy;
constructor(
private readonly noDiscountStrategy: NoDiscountStrategy,
private readonly percentageDiscountStrategy: PercentageDiscountStrategy,
private readonly fixedAmountDiscountStrategy: FixedAmountDiscountStrategy,
) {}
async pay(dto: PaymentRequestDTO) {
const discountedAmount = this.applyDiscount(dto);
// TODO: 결제 로직
}
private applyDiscount(dto: PaymentRequestDTO) {
this.setStrategy(dto.discountType);
this.strategy.applyDiscount(dto);
}
private setStrategy(discountType: string) {
switch (discountType) {
case 'NO_DISCOUNT':
this.strategy = this.noDiscountStrategy;
break;
case 'PERCENTAGE_DISCOUNT':
this.strategy = this.percentageDiscountStrategy;
break;
case 'FIXED_AMOUNT_DISCOUNT':
this.strategy = this.fixedAmountDiscountStrategy;
break;
default:
throw new Error('Invalid product type');
}
}
}
// 4. controller 로직
@Controller('payment')
export class PaymentController {
constructor(private readonly paymentService: PaymentService) {}
@Post()
async pay(@Body() dto: PaymentRequestDTO) {
await this.paymentService.pay(dto);
return { message: 'success' };
}
}
3. 결론
전략 패턴을 사용했을 때의 장점은 책임 분리와 유연성 향상, 그리고 테스트 용이성이다. 유닛 테스트를 진행할 때 service 단에서는 각 전략 별로 별도의 테스트가 가능하고 controller 단에서는 service 내부의 로직과 관계없이 테스트를 작성할 수 있었다.
728x90
반응형