[Design Pattern] Strategy Pattern

이은수, Lee EunSoo·2024년 10월 19일
2

DesignPattern

목록 보기
11/12
post-thumbnail

1. 개요

프로그램을 만들다 보면 여러가지의 알고리즘을 설계하고 상황에 따라 알고리즘을 동적으로 적용해야하는 경우가 있다. 그럴때 쓰면 좋은 패턴이 있다. 바로 스트래티지(strategy) 패턴이다.

심지어 잘만 설계하면 런타임에서 동적으로 변경하도록 만드는것도 가능하다.

2. 설명

2.1. 특징

  • client코드는 사용하고자 하는 알고리즘을 모두 알고 있어야 한다.
  • 알고리즘이 변경될 때 마다 client코드가 변경됨을 해결하고자 함

2.1.1. 의도

다양한 알고리즘들을 캡슐화 해서 서로 교환 가능하도록 하게 구현하기 위함

2.1.2 역할

  • Stretegy
    • 알고리즘들이 공통적으로 가져야 하는 인터페이스를 정의한다.
    • stretegyMethod()
      • context가 알고리즘을 이용할 때 호출되는 메소드
  • Concreate Stretegy
    • stretegyMethod를 구현한다.
    • 구체적인 하나의 알고리즘을 의미한다.
  • Context
    • Stretegy를 이용하는 존재
    • contextMethod
      • Strategy가 Context의 데이터를 접근할 때 사용하는 인터페이스
      • 예제에서는 사용 안됨

2.2. 예제코드

2.2.1. 다이어그램

2.2.2. Java

public enum Hand {
    // 가위 바위 보를 나타내는 세 개의 enum 상수
    ROCK("바위", 0),
    SCISSORS("가위", 1),
    PAPER("보", 2);

    // enum이 가진 필드 
    private String name; // 가위 바위 보 손의 이름
    private int handvalue; // 가위 바위 보 손의 값 

    // 손의 값으로 상수를 얻기 위한 배열
    private static Hand[] hands = {
        ROCK, SCISSORS, PAPER
    };

    // 생성자 
    private Hand(String name, int handvalue) {
        this.name = name;
        this.handvalue = handvalue;
    }

    // 손의 값으로 enum 상수를 가져온다 
    public static Hand getHand(int handvalue) {
        return hands[handvalue];
    }

    // this가 h보다 강할 때 true
    public boolean isStrongerThan(Hand h) {
        return fight(h) == 1;
    }

    // this가 h보다 약할 때 true
    public boolean isWeakerThan(Hand h) {
        return fight(h) == -1;
    }

    // 무승부는 0, this가 이기면 1, h가 이기면 -1
    private int fight(Hand h) {
        if (this == h) {
            return 0;
        } else if ((this.handvalue + 1) % 3 == h.handvalue) {
            return 1;
        } else {
            return -1;
        }
    }

    // 가위 바위 보의 문자열 표현
    @Override
    public String toString() {
        return name;
    }
}

public class Player {
    private String name;
    private Strategy strategy;
    private int wincount;
    private int losecount;
    private int gamecount;

    // 이름과 전략을 받아서 플레이어를 만든다 
    public Player(String name, Strategy strategy) {
        this.name = name;
        this.strategy = strategy;
    }

    // 전략에 따라 다음 손을 결정한다
    public Hand nextHand() {
        return strategy.nextHand();
    }

    // 승리
    public void win() {
        strategy.study(true);
        wincount++;
        gamecount++;
    }

    // 패배
    public void lose() {
        strategy.study(false);
        losecount++;
        gamecount++;
    }

    // 무승부 
    public void even() {
        gamecount++;
    }

    @Override
    public String toString() {
        return "["
            + name + ":"
            + gamecount + " games, "
            + wincount + " win, "
            + losecount + " lose"
            + "]";
    }
}

public interface Strategy {
    public abstract Hand nextHand();
    public abstract void study(boolean win);
}

import java.util.Random;

public class ProbStrategy implements Strategy {
    private Random random;
    private int prevHandValue = 0;
    private int currentHandValue = 0;
    private int[][] history = {
        { 1, 1, 1, },
        { 1, 1, 1, },
        { 1, 1, 1, },
    };

    public ProbStrategy(int seed) {
        random = new Random(seed);
    }

    @Override
    public Hand nextHand() {
        int bet = random.nextInt(getSum(currentHandValue));
        int handvalue = 0;
        if (bet < history[currentHandValue][0]) {
            handvalue = 0;
        } else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {
            handvalue = 1;
        } else {
            handvalue = 2;
        }
        prevHandValue = currentHandValue;
        currentHandValue = handvalue;
        return Hand.getHand(handvalue);
    }

    private int getSum(int handvalue) {
        int sum = 0;
        for (int i = 0; i < 3; i++) {
            sum += history[handvalue][i];
        }
        return sum;
    }

    @Override
    public void study(boolean win) {
        if (win) {
            history[prevHandValue][currentHandValue]++;
        } else {
            history[prevHandValue][(currentHandValue + 1) % 3]++;
            history[prevHandValue][(currentHandValue + 2) % 3]++;
        }
    }
}

import java.util.Random;

public class WinningStrategy implements Strategy {
    private Random random;
    private boolean won = false;
    private Hand prevHand;

    public WinningStrategy(int seed) {
        random = new Random(seed);
    }

    @Override
    public Hand nextHand() {
        if (!won) {
            prevHand = Hand.getHand(random.nextInt(3));
        }
        return prevHand;
    }

    @Override
    public void study(boolean win) {
        won = win;
    }
}

import java.util.Random;

public class RandomStrategy implements Strategy {
    private Random random;

    public RandomStrategy(int seed) {
        random = new Random(seed);
    }

    @Override
    public void study(boolean win) {
    }

    @Override
    public Hand nextHand() {
        return Hand.getHand(random.nextInt(3));
    }
}

public class Main {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage: java Main randomseed1 randomseed2");
            System.out.println("Example: java Main 314 15");
            System.exit(0);
        }
        int seed1 = Integer.parseInt(args[0]);
        int seed2 = Integer.parseInt(args[1]);
        Player player1 = new Player("Taro", new ProbStrategy(seed1));
        Player player2 = new Player("Hana", new RandomStrategy(seed2));
        for (int i = 0; i < 10000; i++) {
            Hand nextHand1 = player1.nextHand();
            Hand nextHand2 = player2.nextHand();
            if (nextHand1.isStrongerThan(nextHand2)) {
                System.out.println("Winner:" + player1);
                player1.win();
                player2.lose();
            } else if (nextHand2.isStrongerThan(nextHand1)) {
                System.out.println("Winner:" + player2);
                player1.lose();
                player2.win();
            } else {
                System.out.println("Even...");
                player1.even();
                player2.even();
            }
        }
        System.out.println("Total result:");
        System.out.println(player1.toString());
        System.out.println(player2.toString());
    }
}

이 코드에서는 특이하게 열거형이 사용된다.

2.2.3. Swift

enum Hand: Int, CustomStringConvertible{
    case Rock = 0
    case Scissors = 1
    case Paper = 2
    
    // 열거형을 배열로 저장하여 값을 가져오기 쉽게 함
    private static var hands: [Hand] = [.Rock, .Scissors, .Paper]
       
    private var name: String {
        switch self {
        case .Rock: return "ROCK"
        case .Scissors: return "SCISSORS"
        case .Paper: return "PAPER"
        }
    }
    
    private var handValue: Int {
        switch self {
        case .Rock: return 0
        case .Scissors: return 1
        case .Paper: return 2
        }
    }
    
    static func getHand(_ handValue: Int) -> Hand {
        return hands[handValue]
    }
    
    func isStrongerThan(_ h: Hand) -> Bool {
        return fight(h) == 1
    }
    
    func isWeakerThan(_ h: Hand) -> Bool {
        return fight(h) == -1
    }
    
    func fight(_ h: Hand) -> Int{
        if self == h {
            return 0
        }
        else if ((self.handValue + 1) % 3 == h.handValue){
            return 1
        }
        else{
            return -1
        }
    }
    
    var description: String {
        switch self {
        case .Rock: return "Rock"
        case .Scissors: return "Scissors"
        case .Paper: return "Paper"
        }
    }
}

protocol Strategy {
    func nextHand() -> Hand
    func study(_ win: Bool)
}

class ProbStrategy: Strategy {
//    private var random: Int
    private var seed: Int
    
    private var prevHandValue: Int = 0
    private var currentHandValue: Int = 0
    
    private var history = [
        [1,1,1, ],
        [1,1,1, ],
        [1,1,1, ]
    ]
    
    init(_ seed: Int) {
        self.seed = seed
    }
    
    func nextHand() -> Hand {
        let bet: Int = Int.random(in: 0...getSum(prevHandValue)-1)
        var handvalue: Int = 0
        
        if (bet < history[currentHandValue][0]){
            handvalue = 0
        }else if(bet < history[currentHandValue][0] + history[currentHandValue][1]){
            handvalue = 1
        }else{
            handvalue = 2
        }
        
        prevHandValue = currentHandValue
        currentHandValue = handvalue
        return Hand.getHand(handvalue)
    }
    
    private func getSum(_ handvalue: Int) -> Int{
        var sum: Int = 0
        for i in 0..<3{
            sum += history[handvalue][i]
        }
        return sum
    }
    
    
    func study(_ win: Bool) {
        if(win){
            history[prevHandValue][currentHandValue] += 1
        }else{
            history[prevHandValue][(currentHandValue + 1) % 3] += 1
            history[prevHandValue][(currentHandValue + 2) % 3] += 1
        }
    }
}

class RandomStrategy: Strategy {
    private let seed: Int
    
    init(_ seed: Int) {
        self.seed = seed
    }
    
    func study(_ win: Bool) {
    }
    func nextHand() -> Hand {
        return Hand.getHand(Int.random(in: 0..<3))
    }
}

class WinningStrategy : Strategy{
    private var won : Bool = false
    private var prevHand: Hand?
    var seed: Int = 0
    
    init(_ seed: Int){
        self.seed = seed
    }
    
    func nextHand() -> Hand {
        if(!won){
            prevHand = Hand.getHand(Int.random(in: 0..<3))
        }
        return prevHand!
    }
    func study(_ win: Bool){
        won = win
    }
}

class Player: CustomStringConvertible{
    private var name: String
    private var strategy: Strategy
    var wincount: Int = 0
    var losecount: Int = 0
    var gamecount: Int = 0
    
    init(_ name: String,_ strategy: Strategy){
        self.name = name
        self.strategy = strategy
    }
    
    func nextHand() -> Hand{
        return strategy.nextHand()
    }
    
    func win(){
        strategy.study(true)
        wincount += 1
        gamecount += 1
    }
    
    func lose(){
        strategy.study(false)
        losecount += 1
        gamecount += 1
    }
    func even(){
        gamecount += 1
    }
    
    var description: String{
        return "[\(name): \(gamecount) games, \(wincount) wins, \(losecount) lose]"
    }
}

@main
struct Main {
    static func main() {
        var seed1: Int = 10
        var seed2: Int = 100
        
        print("insert seed 1>> ")
        seed1 = Int(readLine()!)!
        print("insert seed 2>> ")
        seed2 = Int(readLine()!)!
        
        var player1 = Player("Taro", ProbStrategy(seed1))
        var player2 = Player("Hana", RandomStrategy(seed2))
        
        for i in 0...1000{
            var nextHand1 = player1.nextHand()
            var nextHand2 = player2.nextHand()
            
            if(nextHand1.isStrongerThan(nextHand2)){
                print("Winner: \(player1)")
                player1.win()
                player2.lose()
            }else if(nextHand2.isStrongerThan(nextHand1)){
                print("Winner: \(player2)")
                player1.lose()
                player2.win()
            }else{
                print("Even...")
                player1.even()
                player2.even()
            }
        }
        print("Total Result:\n\(player1)\n\(player2)")
    }
}
  • swift의 열거형의 경우

    • 저장 프로퍼티를 사용하는것이 불가능해서 조금 다른형태로 구현하였다.
  • toString의 경우

    • 다른 게시글에서 그러한것처럼 CustomStringConvertible프로토콜을 이용해서 description 프로퍼티를 이용해 동일한 형태를 이루도록 만들었다.
  • random의 경우

    • swift와 자바의 랜덤사용방법이 조금 다른점 참고

3. 정리

스트래티지패턴은 여러가지 알고리즘을 사용하는 프로그램에서 알고리즘의 추가와 동적인 알고리즘 교체에 유용한 디자인 패턴이다.

profile
iOS 개발자 취준생, 천 리 길도 한 걸음부터

0개의 댓글