【图解设计模式】Strategy模式

整体地替换算法的实现部分,能轻松地以不同的算法去解决同一个问题。

示例

让电脑玩“猜拳”游戏。
第一种策略是“如果这局猜拳获胜,那么下一局也出一样的手势”;另外一种策略是“根据上一局的手势从概率上计算出下一局的手势”。

类图

eyh5Qg.png

Hand类

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
public class Hand {
public static final int HANDVALUE_GUU = 0;
public static final int HANDVALUE_CHO = 1;
public static final int HANDVALUE_PAA = 2;

public static final Hand[] hand = {
new Hand(HANDVALUE_GUU),
new Hand(HANDVALUE_CHO),
new Hand(HANDVALUE_PAA)
};

public static final String[] name = {
"石头", "剪刀", "布"
};

private int handvalue;

private Hand(int handvalue) {
this.handvalue = handvalue;
}

public static Hand getHand(int handvalue) {
return hand[handvalue];
}

public boolean isStrongerThan(Hand h) {
return fight(h) == 1;
}

public boolean isWeakerThan(Hand h) {
return fight(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[handvalue];
}
}

Strategy接口

1
2
3
4
public interface Strategy {
public abstract Hand nextHand();
public abstract void study(boolean win);
}

WinningStrategy类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}
}

ProbStrategy类

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
import java.util.Random;

public class ProbStrategy implements Strategy {
private Random random;
private int prevHandValue = 0;
private int currentHandValue = 0;
// history[上一局出的手势][这一局出的手势]
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;

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);
}

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

private int getSum(int hv) {
int sum = 0;

for (int i = 0; i < 3; i++)
sum += history[hv][i];

return sum;
}
}

Player类

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
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" + "]";
}
}

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
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 WinningStrategy(seed1));
Player player2 = new Player("Hana", new ProbStrategy(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 (nextHand1.isWeakerThan(nextHand2)) {
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());
}
}

运行结果

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
Even...
Winner:[Hana:1 games, 0 win, 0 lose]
Winner:[Taro:2 games, 0 win, 1 lose]
Even...
Winner:[Hana:4 games, 1 win, 1 lose]
Winner:[Taro:5 games, 1 win, 2 lose]
Even...
Even...
Winner:[Taro:8 games, 2 win, 2 lose]
Winner:[Taro:9 games, 3 win, 2 lose]
Winner:[Taro:10 games, 4 win, 2 lose]
Even...

...

Even...
Winner:[Taro:9992 games, 3164 win, 3488 lose]
Winner:[Hana:9993 games, 3488 win, 3165 lose]
Winner:[Taro:9994 games, 3165 win, 3489 lose]
Winner:[Taro:9995 games, 3166 win, 3489 lose]
Winner:[Hana:9996 games, 3489 win, 3167 lose]
Even...
Even...
Even...
Total result:
[Taro:10000 games, 3167 win, 3490 lose]
[Hana:10000 games, 3490 win, 3167 lose]

登场角色

Strategy(策略)

Strategy角色负责决定实现策略所必需的接口(API)。在示例程序中,由Strategy接口扮演此角色。

ConcreteStrategy(具体的策略)

ConcreteStrategy角色负责实现Strategy角色的接口(API),即负责实现具体的策略(战略、方向、方法和算法)。在示例程序中,由WinningStrategy类和ProbStrategy类扮演此角色。

Context(上下文)

负责使用Strategy角色。Context角色保存了ConcreteStrategy角色的实例,并使用ConcreteStrategy角色去实现需求(总之,还是要调用Strategy角色的接口(API))。在示例程序中,由Player类扮演此角色。

类图

e6hOC6.png