포스트

[Behavioral Pattern] 전략 패턴 (Strategy Pattern)

Design Pattern / Behavioral Pattern

전략 패턴의 정의와 해당 디자인 패턴의 예제 코드를 통한 이해 및 설명 정리

개념

  • 실행(런타일) 중에 알고리즘 전략을 선택하여 객체 동작을 실시간으로 바뀌도록 할 수 있게 하는 행위 디자인 패턴
  • 여기서 ‘전략’이란 일종의 알고리즘이 될 수도 있으며, 기능이나 동작이 될 수도 있는 특정한 목표를 수행하기 위한 행동 계획을 말함

  • 즉, 어떤 일을 수행하는 알고리즘이 여러가지일 때, 동작들을 미리 전략으로 정의함으로써 손쉽게 전략을 교체할 수 있는, 알고리즘 변형이 빈번하게 필요한 경우에 적합한 패턴

패턴 구조

strategy

  • ConcreteStrategy1~3 (전략 알고리즘 객체들)

    • 알고리즘, 행위, 동작을 객체로 정의한 구현체
  • Strategy (전략 인터페이스)

    • 모든 전략 구현체에 대한 공용 인터페이스
  • Context (컨텍스트)

    • 알고리즘을 실행해야 할 때마다 해당 알고리즘과 연결된 전략 객체의 메소드를 호출
  • Client (클라이언트)

    • 특정 전략 객체를 컨텍스트에 전달 함으로써 전략을 등록하거나 변경하여 전략 알고리즘을 실행한 결과를 누림

GoF에서의 전략 패턴의 정의

  1. 동일 계열의 알고리즘군을 정의하고

    전략 구현체로 정의

  2. 각각의 알고리즘을 캡슐화하여

    인터페이스로 추상화

  3. 이들을 상호 교환이 가능하도록 만들고,

    합성(composition)으로 구성

  4. 알고리즘을 사용하는 클라이언트와 상관없이 독립적으로

    컨텍스트 객체 수정 없이

  5. 알고리즘을 다양하게 변경할 수 있게 함

    메소드를 통해 전략 객체를 실시간으로 변경함으로써 전략을 변경

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Run | Walk 전략 (추상화된 알고리즘)
interface MoveStrategy {
  move(): void;
}

class Walk implements MoveStrategy {
  public move(): void {
    console.log("걸어서 배달합니다.");
  }
}

class Run implements MoveStrategy {
  public move(): void {
    console.log("뛰어서 배달합니다.");
  }
}

// 한국어 | 일본어 번역 전략 (추상화된 알고리즘)
interface TranslateStrategy {
  translate(): void;
}

class Korean implements TranslateStrategy {
  public translate(): void {
    console.log("한국어로 번역합니다.");
  }
}

class Japanese implements TranslateStrategy {
  public translate(): void {
    console.log("일본어로 번역합니다.");
  }
}

// Context (전략 등록 / 실행)
class Robot {
  moveStrategy: MoveStrategy;
  translateStrategy: TranslateStrategy;

  constructor(
    moveStrategy: MoveStrategy,
    translateStrategy: TranslateStrategy
  ) {
    this.moveStrategy = moveStrategy;
    this.translateStrategy = translateStrategy;
  }

  move(): void {
    this.moveStrategy.move();
  }

  translate(): void {
    this.translateStrategy.translate();
  }

  setMove(moveStrategy: MoveStrategy) {
    this.moveStrategy = moveStrategy;
  }

  setTranslate(translateStrategy: TranslateStrategy) {
    this.translateStrategy = translateStrategy;
  }
}

// Client (전략 교체 / 전략 실행한 결과를 얻음)
class User {
  public static main(_args?: string[]): void {
    const robot: Robot = new Robot(new Walk(), new Korean());
    robot.move(); // 걸어서 배달합니다.
    robot.translate(); // 한국어로 번역합니다.

    console.log("");

    // 로봇의 전략(기능)을 Run과 일본어 번역으로 변경
    robot.setMove(new Run());
    robot.setTranslate(new Japanese());

    robot.move(); // 뛰어서 배달합니다.
    robot.translate(); // 일본어로 번역합니다.
  }
}

User.main();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// 전략 (추상화된 알고리즘)
interface PaymentStrategy {
  pay(amount: number): void;
}

class KakaoCardStrategy implements PaymentStrategy {
  private name: string;
  private cardNumber: number;
  private cvv: number;
  private dateOfExpire: string;

  constructor(
    name: string,
    cardNumber: number,
    cvv: number,
    dateOfExpire: string
  ) {
    this.name = name;
    this.cardNumber = cardNumber;
    this.cvv = cvv;
    this.dateOfExpire = dateOfExpire;
  }

  public pay(amount: number): void {
    console.log(`${amount}₩ paid using Kakao Card`);
  }
}

class LunaCardStrategy implements PaymentStrategy {
  private email: string;
  private password: string;

  constructor(email: string, password: string) {
    this.email = email;
    this.password = password;
  }

  public pay(amount: number): void {
    console.log(`${amount}₩ paid using Luna Card`);
  }
}

class Item {
  public name: string;
  public price: number;

  constructor(name: string, price: number) {
    this.name = name;
    this.price = price;
  }
}

// Context (전략 등록 / 실행)
class ShoppingCart {
  items: Item[];

  constructor() {
    this.items = new Array<Item>();
  }

  public addItem(item: Item): void {
    this.items.push(item);
  }

  // 전략은 매개변수로 받아서 바로 전략을 실행
  public pay(paymentMethod: PaymentStrategy): void {
    let amount: number = 0;
    for (let item of this.items) {
      amount += item.price;
    }

    paymentMethod.pay(amount);
  }
}

// Client (전략 제공 / 설정)
class PayUser {
  public static main(_args?: string[]): void {
    // 쇼핑카트 전략 Context 등록
    const cart: ShoppingCart = new ShoppingCart();

    // 쇼핑 물품
    const A: Item = new Item("맥북 에어", 10000);
    const B: Item = new Item("맥북 프로", 30000);

    cart.addItem(A);
    cart.addItem(B);

    // Luna Card로 결제 전략 실행
    cart.pay(new LunaCardStrategy("han1210_36@naver.com", "password1234"));

    // Kakao Card로 결제 전략 실행
    cart.pay(new KakaoCardStrategy("HyungJinHan", 123456789, 123, "12/10"));
  }
}

PayUser.main();

참고한 출처 사이트

Refactoring GURU

Inpa Dev Blog (디자인 패턴)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.