设计模式(十)Strategy模式

  Strategy模式,就是用来整体地替换算法,可以轻松地以不同的算法解决同一个问题。

  还是根据一个示例程序来理解这种设计模式吧。先看一下示例程序的类图。

  然后看示例程序代码。

 1 package bigjunoba.bjtu.strategy;
 2 
 3 public class Hand {
 4     public static final int HANDVALUE_GUU = 0;  // 表示石头的值
 5     public static final int HANDVALUE_CHO = 1;  // 表示剪刀的值
 6     public static final int HANDVALUE_PAA = 2;  // 表示布的值
 7     public static final Hand[] hand = {         // 表示猜拳中3种手势的实例
 8         new Hand(HANDVALUE_GUU),
 9         new Hand(HANDVALUE_CHO),
10         new Hand(HANDVALUE_PAA),
11     };
12     private static final String[] name = {      // 表示猜拳中手势所对应的字符串
13         "石头", "剪刀", "布",
14     };
15     private int handvalue;                      // 表示猜拳中出的手势的值
16     private Hand(int handvalue) {
17         this.handvalue = handvalue;
18     }
19     public static Hand getHand(int handvalue) { // 根据手势的值获取其对应的实例
20         return hand[handvalue];
21     }
22     public boolean isStrongerThan(Hand h) {     // 如果this胜了h则返回true
23         return fight(h) == 1;
24     }
25     public boolean isWeakerThan(Hand h) {       // 如果this输给了h则返回true
26         return fight(h) == -1;
27     }
28     private int fight(Hand h) {                 // 计分:平0, 胜1, 负-1
29         if (this == h) {
30             return 0;
31         } else if ((this.handvalue + 1) % 3 == h.handvalue) {
32             return 1;
33         } else {
34             return -1;
35         }
36     }
37     public String toString() {                  // 转换为手势值所对应的字符串
38         return name[handvalue];
39     }
40 }

  Hand类是用来表示猜拳中“手势”的类,首先创建了Hand类的实例,并将它们保存在hand数组中。getHand方法的作用是,将手势的值作为参数传递给getHand方法,它就会将手势的值对应的Hand类的实例返回。判断猜拳结果比较有意思,如果hand1赢了hand2,那么可以用hand1.isStrongerThan(hand2)来表示,反之如果hand1输了hand2,那么可以用hand1.isWeakerThan(hand2)来表示。fight方法是用来比较this和h的,如果this的手势值加1后是h的手势值,那么this获胜。例如this是石头(0),h是剪刀(1);或者this是剪刀(1)而h是布(2);或者this是布(2)而h是石头(0)。这里的取余是因为2加上1后除以3的余数正好是0,也就是石头。这里的Hand类被其他类使用,但是它不是strategy模式的一部分。

1 package bigjunoba.bjtu.strategy;
2 
3 public interface Strategy {
4     public abstract Hand nextHand();
5     public abstract void study(boolean win);
6 }

  Strategy接口定义了猜拳策略的抽象方法接口。nextHand方法的作用是“获取下一局要出的手势”,study方法是学习“上一局的手势是否获胜了”。

 1 package bigjunoba.bjtu.strategy;
 2 
 3 import java.util.Random;
 4 
 5 public class WinningStrategy implements Strategy {
 6     private Random random;
 7     private boolean won = false;
 8     private Hand prevHand;
 9     public WinningStrategy(int seed) {
10         random = new Random(seed);
11     }
12     public Hand nextHand() {
13         if (!won) {
14             prevHand = Hand.getHand(random.nextInt(3));
15         }
16         return prevHand;
17     }
18     public void study(boolean win) {
19         won = win;
20     }
21 }

  WinningStrategy类实现了Strategy接口。这种猜拳策略是,如果上一局的手势赢了,则下一局的手势就与上局相同;如果上一局手势输了,那下一局就随机出手势。won字段中保存的是上一局猜拳的输赢结果,如果上一局赢了,那么won值为true,然后nextHand方法直接返回prevHand,study方法调用study(True)。

 1 package bigjunoba.bjtu.strategy;
 2 
 3 import java.util.Random;
 4 
 5 public class ProbStrategy implements Strategy {
 6     private Random random;
 7     private int prevHandValue = 0;
 8     private int currentHandValue = 0;
 9     private int[][] history = {
10         { 1, 1, 1, },
11         { 1, 1, 1, },
12         { 1, 1, 1, },
13     };
14     public ProbStrategy(int seed) {
15         random = new Random(seed);
16     }
17     public Hand nextHand() {
18         int bet = random.nextInt(getSum(currentHandValue));
19         int handvalue = 0;
20         if (bet < history[currentHandValue][0]) {
21             handvalue = 0;
22         } else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {
23             handvalue = 1;
24         } else {
25             handvalue = 2;
26         }
27         prevHandValue = currentHandValue;
28         currentHandValue = handvalue;
29         return Hand.getHand(handvalue);
30     }
31     private int getSum(int hv) {
32         int sum = 0;
33         for (int i = 0; i < 3; i++) {
34             sum += history[hv][i];
35         }
36         return sum;
37     }
38     public void study(boolean win) {
39         if (win) {
40             history[prevHandValue][currentHandValue]++;
41         } else {
42             history[prevHandValue][(currentHandValue + 1) % 3]++;
43             history[prevHandValue][(currentHandValue + 2) % 3]++;
44         }
45     }
46 }

  ProbStrategy类是随机出手势,但是每种手势出现的概率会根据以前的猜拳结果而改变。这里的理解就是,假如上一局出的是石头,那么history【0】【0】表示两局分别出石头和石头时胜了的次数,同理history【0】【1】表示两局分别出石头和剪刀时胜了的次数,history【0】【2】表示两局分别出石头和布时胜了的次数。这三个的值假如是3/5/7的情况下,下一局就会以石头、剪刀和布的比率为3:5:7来决定,在0到15之间取一个随机数,如果随机数是0 1 2那么出石头,如果随机数在3 4 5 6 7 那么出剪刀,如果随机数是9 10 11 12 13 14 15那么出布。

 1 package bigjunoba.bjtu.strategy;
 2 
 3 public class Player {
 4     private String name;
 5     private Strategy strategy;
 6     private int wincount;
 7     private int losecount;
 8     private int gamecount;
 9     public Player(String name, Strategy strategy) {         // 赋予姓名和策略
10         this.name = name;
11         this.strategy = strategy;
12     }
13     public Hand nextHand() {                                // 策略决定下一局要出的手势
14         return strategy.nextHand();
15     }
16     public void win() {                 //
17         strategy.study(true);
18         wincount++;
19         gamecount++;
20     }
21     public void lose() {                //
22         strategy.study(false);
23         losecount++;
24         gamecount++;
25     }
26     public void even() {                //
27         gamecount++;
28     }
29     public String toString() {
30         return "[" + name + ":" + gamecount + " games, " + wincount + " win, " + losecount + " lose" + "]";
31     }
32 }

  Player类表示进猜拳游戏选手的类。nextHand方法的返回值就是策略的nextHand方法的返回值,也就是将自己的工作委托给了strategy

 1 package bigjunoba.bjtu.strategy;
 2 
 3 public class Main {
 4     public static void main(String[] args) {
 5         if (args.length != 2) {
 6             System.out.println("Usage: java Main randomseed1 randomseed2");
 7             System.out.println("Example: java Main 314 15");
 8             System.exit(0);
 9         }
10         int seed1 = Integer.parseInt(args[0]);
11         int seed2 = Integer.parseInt(args[1]);
12         Player player1 = new Player("Lianjiang", new WinningStrategy(seed1));
13         Player player2 = new Player("Qiaoye", new ProbStrategy(seed2));
14         for (int i = 0; i < 20; i++) {
15             Hand nextHand1 = player1.nextHand();
16             Hand nextHand2 = player2.nextHand();
17             if (nextHand1.isStrongerThan(nextHand2)) {
18                 System.out.println("Winner:" + player1);
19                 player1.win();
20                 player2.lose();
21             } else if (nextHand2.isStrongerThan(nextHand1)) {
22                 System.out.println("Winner:" + player2);
23                 player1.lose();
24                 player2.win();
25             } else {
26                 System.out.println("Even...");
27                 player1.even();
28                 player2.even();
29             }
30         }
31         System.out.println("Total result:");
32         System.out.println(player1.toString());
33         System.out.println(player2.toString());
34     }
35 }

  main类负责让电脑进行猜拳游戏。Lianjiang和Qiaoye分别使用不同的策略进行了100局比赛。这里必须输入两个数作为随机数的种子,关于这一方面,目前还是不太理解,等到理解了再来解释。

Even...
Winner:[Qiaoye:1 games, 0 win, 0 lose]
Winner:[Lianjiang:2 games, 0 win, 1 lose]
Even...
Winner:[Qiaoye:4 games, 1 win, 1 lose]
Winner:[Lianjiang:5 games, 1 win, 2 lose]
Even...
Even...
Winner:[Lianjiang:8 games, 2 win, 2 lose]
Winner:[Lianjiang:9 games, 3 win, 2 lose]
Winner:[Lianjiang:10 games, 4 win, 2 lose]
Even...
Winner:[Qiaoye:12 games, 2 win, 5 lose]
Even...
Winner:[Lianjiang:14 games, 5 win, 3 lose]
Winner:[Qiaoye:15 games, 3 win, 6 lose]
Winner:[Qiaoye:16 games, 4 win, 6 lose]
Winner:[Lianjiang:17 games, 6 win, 5 lose]
Winner:[Qiaoye:18 games, 5 win, 7 lose]
Even...
Total result:
[Lianjiang:20 games, 7 win, 6 lose]
[Qiaoye:20 games, 6 win, 7 lose]

  输出结果如上,进行了20局的结果。

  Strategy模式类图如下:

  Strategy模式主要思想就是将算法与其他部分分离开,只定义了与算法相关的接口,然后在程序中以委托的方式来使用算法。使用委托这种弱关联关系可以很方便地整体替换算法,或者选择更好的算法在不同的环境下运行。

 

posted @ 2018-04-01 11:12  BigJunOba  阅读(262)  评论(0编辑  收藏  举报