프로그램을 만들다 보면 여러가지의 알고리즘을 설계하고 상황에 따라 알고리즘을 동적으로 적용해야하는 경우가 있다. 그럴때 쓰면 좋은 패턴이 있다. 바로 스트래티지(strategy) 패턴이다.
심지어 잘만 설계하면 런타임에서 동적으로 변경하도록 만드는것도 가능하다.
다양한 알고리즘들을 캡슐화 해서 서로 교환 가능하도록 하게 구현하기 위함
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());
}
}
이 코드에서는 특이하게 열거형이 사용된다.
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의 경우
random의 경우
스트래티지패턴은 여러가지 알고리즘을 사용하는 프로그램에서 알고리즘의 추가와 동적인 알고리즘 교체에 유용한 디자인 패턴이다.