본문 바로가기
필수 개발지식/디자인패턴

[Design Pattern] 전략 패턴 (Strategy)

by 코딩하는짱구 2023. 5. 23.
반응형

 

✅행동패턴의 전략 패턴(Strategy Pattern)이란?

전략 패턴은 실행(런타임) 중에 알고리즘 전략을 선택하여 객체 동작을 실시간으로 바뀌도록 할 수 있게 하는 행위 디자인 패턴 이다. 즉, 어떤 일을 수행하는 알고리즘이 여러가지 일때, 동작들을 미리 전략으로 정의함으로써 손쉽게 전략을 교체할 수 있는, 알고리즘 변형이 빈번하게 필요한 경우에 적합한 패턴이다.

 

 

네비게이터 앱을 만들어본다고 가정하자!

넌 어떻게 갈꺼야?

 

 

 

 

네비게이터에는 자동차를 위한 길, 걸어갈 길, 대중교통을 이용할 길 등 많은 옵션이 계속 추가될 것이다. 

자동차타고 갈 길, 걸어갈 길, 대중교통 이용한 길 등 여러 옵션이 추가된다.

 

 

 

 

 

 

 

전략패턴(Strategy Pattern)의 구조

  • 전략패턴에선 크게 Context, Strategies 두 가지의 요소가 중요하다. 
  • Context : Navigater, Strategies: 여러 옵션의 길
  • 클래스를 선택 후 모든 알고리즘을 전략들(strategies) 라는 별도의 클래스들로 추출한다.
  • 컨텍스트에서 사용할 알고리즘을 클라이언트가 선택한다.

여러 알고리즘을 캡슐화하고 상호 교환 가능하게 만든다.

 

 

프로그래밍에서의 ​컨텍스트(Context) 란 콘텐츠(Contetns)를 담는 그 무엇인가를 뜻한며, 어떤 객체를 핸들링 하기 위한 접근 수단이다. 즉, 물컵에 물이 담겨있으면 물은 콘텐츠가 되고, 물컵은 컨텍스트가 되며, 물을 핸들링 하기 위한 접근 수단이 된다.

 

 

 

 

 

전략패턴(Strategy Pattern)은 어떻게 쓰일까?


1. 가위 바위 보 

컴퓨터가 전략을 바꿔가며 대응하는 것이다.

가위, 바위, 보를 객체 구현체로 정의하고, 이들을 Strategy라는 인터페이스로 묶어 주었다. 

 

context : Game

strategy : Strategy

strategies : ScissorsStrategy, RockStrategy, PaperStrategy

const readline = require('readline');

// 전략 인터페이스
class Strategy {
  play() {
    throw new Error('play 메서드를 구현해야 합니다.');
  }
}

// 가위 전략
class ScissorsStrategy extends Strategy {
  play() {
    return '가위';
  }
}

// 바위 전략
class RockStrategy extends Strategy {
  play() {
    return '바위';
  }
}

// 보 전략
class PaperStrategy extends Strategy {
  play() {
    return '';
  }
}

// 컨텍스트
class Game {
  constructor(strategy) {
    this.strategy = strategy;
    this.rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });
  }

  playGame() {
    this.rl.question('가위, 바위, 보 중 하나를 선택하세요: ', (userChoice) => {
      const computerChoice = this.strategy.play();

      console.log('사용자 선택:', userChoice);
      console.log('컴퓨터 선택:', computerChoice);

      this.rl.close();

      // 게임 로직을 구현하고 결과를 출력하는 부분은 생략했습니다.
    });
  }
}

// 사용 예시
const userStrategy = new ScissorsStrategy(); // 사용자가 가위를 선택했다고 가정
const game = new Game(userStrategy);
game.playGame();

 

 

 


2. 게임에서 캐릭터의 무기 전략

적의 특성에 따라 주인공이 무기 전략을 바꿔가며 대응하는 것이다.

 

context : Character 

strategy : WeaponStrategy

strategies : SwordStrategy, BowStrategy, MagicStrategy

// 전략 인터페이스
class WeaponStrategy {
  useWeapon() {
    throw new Error('useWeapon 메서드를 구현해야 합니다.');
  }
}

// 검 전략
class SwordStrategy extends WeaponStrategy {
  useWeapon() {
    console.log('검으로 공격합니다.');
  }
}

// 활 전략
class BowStrategy extends WeaponStrategy {
  useWeapon() {
    console.log('활로 공격합니다.');
  }
}

// 마법 전략
class MagicStrategy extends WeaponStrategy {
  useWeapon() {
    console.log('마법으로 공격합니다.');
  }
}

// 캐릭터
class Character {
  constructor(name, enemyType) {
    this.name = name;
    this.enemyType = enemyType;
    this.weaponStrategy = null;
  }

  setWeaponStrategy() {
    if (this.enemyType === '둥근몬스터') {
      this.weaponStrategy = new SwordStrategy();
    } else if (this.enemyType === '멀티팔몬스터') {
      this.weaponStrategy = new BowStrategy();
    } else {
      this.weaponStrategy = new MagicStrategy();
    }
  }

  attack() {
    console.log(`${this.name}이(가) ${this.enemyType}에게 공격을 시도합니다.`);
    this.weaponStrategy.useWeapon();
  }
}

// 사용 예시
const character1 = new Character('전사', '둥근몬스터');
character1.setWeaponStrategy();
character1.attack(); // 검으로 공격합니다.

const character2 = new Character('궁수', '멀티팔몬스터');
character2.setWeaponStrategy();
character2.attack(); // 활로 공격합니다.

const character3 = new Character('마법사', '날개있는몬스터');
character3.setWeaponStrategy();
character3.attack(); // 마법으로 공격합니다.

 

 

 

 

 

 


3. 결제 방식 

context : PaymentContext 

strategy : PaymentStrategy

strategies : CreditCardStrategy, PaypalStrategy, CashStrategy

// 전략 인터페이스
class PaymentStrategy {
  pay(amount) {
    throw new Error('pay 메서드를 구현해야 합니다.');
  }
}

// 신용카드 결제 전략
class CreditCardStrategy extends PaymentStrategy {
  pay(amount) {
    console.log(`신용카드로 $${amount} 결제를 진행합니다.`);
    // 신용카드 결제 처리 로직을 구현합니다.
  }
}

// 페이팔 결제 전략
class PaypalStrategy extends PaymentStrategy {
  pay(amount) {
    console.log(`Paypal로 $${amount} 결제를 진행합니다.`);
    // 페이팔 결제 처리 로직을 구현합니다.
  }
}

// 현금 결제 전략
class CashStrategy extends PaymentStrategy {
  pay(amount) {
    console.log(`현금으로 $${amount} 결제를 진행합니다.`);
    // 현금 결제 처리 로직을 구현합니다.
  }
}

// 결제 컨텍스트
class PaymentContext {
  constructor(strategy) {
    this.strategy = strategy;
  }

  setPaymentStrategy(strategy) {
    this.strategy = strategy;
  }

  processPayment(amount) {
    this.strategy.pay(amount);
  }
}

// 사용 예시
const paymentContext = new PaymentContext(new CreditCardStrategy()); // 기본적으로 신용카드 결제 전략을 사용

paymentContext.processPayment(100); // 신용카드로 $100 결제를 진행합니다.

paymentContext.setPaymentStrategy(new PaypalStrategy()); // 페이팔 결제 전략으로 변경
paymentContext.processPayment(50); // Paypal로 $50 결제를 진행합니다.

paymentContext.setPaymentStrategy(new CashStrategy()); // 현금 결제 전략으로 변경
paymentContext.processPayment(200); // 현금으로 $200 결제를 진행합니다.

 

 

 

전략패턴(Strategy Pattern)의 장단점?

장점

  • 새로운 전략을 추가해도 기존 코드를 변경하지 않는다.
  • 상속대신 위임을 사용할 수 있다.
  • 런타임에 전략을 변경할 수 있다.

 

**상속vs위임

상속은 객체 간의 정적인 관계를 형성하며, 부모 클래스의 기능을 자식 클래스가 상속받아 사용하는 방식이다. 이는 상속 계층이 고정되어 있고, 부모 클래스의 변경이 자식 클래스에 영향을 미칠 수 있다는 한계가 있다. 또한, 다중 상속의 경우 복잡성과 모호성 문제가 발생할 수 있다.

 

전략 패턴에서는 전략 인터페이스를 정의하고, 해당 인터페이스를 구현한 여러 전략 클래스들을 생성한다. 이때 컨텍스트 객체는 전략 객체를 필요에 따라 교환하고, 해당 전략 객체에 작업을 위임한다. 이렇게 위임을 사용하면 컨텍스트는 실행 시에 다양한 전략을 사용할 수 있으며, 새로운 전략을 추가하거나 변경하는 데 용이하다. 이는 상속을 사용한 정적인 관계보다 유연하고 확장성이 높은 구조를 가능하게 한다.

 

 

 

단점

  • 복잡도가 증가한다.
  • 클라이언트 코드가 구체적인 전략을 알아야 한다.
반응형