Author:Gorit
Date:2021年2月16日
Refer:《图解设计模式》

10.1 Strategy 模式

“Strategy”指的是 “策略” 模式,指的是与敌军对垒时的行军作战的方法。在代码的领域中,我们可以将其理解为“算法”

设计程序的目的,为了解决特定的问题,而为了解决问题,我们就要编写特定的算法。使用 Strategy 可以整体地替换算法的实现部分。能够整体地替换算法,能让我们轻松地以不同的算法解决同一类问题,这就是 策略模式

10.2 示例程序

10.2.1 程序功能

我们要实现一段使用了 Strategy 模式的示例程序。程序要实现的功能是让电脑玩“猜拳”游戏。

我们考虑两种猜拳策略(设计猜拳的算法规则):

  1. “如果这局猜拳获胜,那么下一句继续出一样的手势”
  2. “根据上一局的手势从概率上计算下一局的手势”

10.2.2 类和接口一览表

名字 说明
Hand 表示猜拳游戏中的“手势”
Strategy 表示猜拳游戏中策略的类
WinningStrategy 表示上述策略 1
ProbStrategy 表示上述策略 2
Player 表示进行猜拳游戏的选手的类
Main 测试程序行为的类

10.2.3 程序具体实现

Hand 类

  1. package strategy;
  2. /**
  3. * 表示程序猜拳手势的类。在该类的内部
  4. * int 表示手势,0 石头,1 表示剪刀,2 表示布,将值保存在 handValue 字段中
  5. * 我们需要三个 Hand 实例,保存在
  6. */
  7. public class Hand {
  8. public static final int HANDVALUE_GUU = 0; // 石头
  9. public static final int HANDVALUE_CHO = 1; // 剪刀
  10. public static final int HANDVALUE_PAA = 2; // 布
  11. public static final Hand[] hand = { // 表示猜拳中 3 种手势的实例
  12. new Hand(HANDVALUE_GUU),
  13. new Hand(HANDVALUE_CHO),
  14. new Hand(HANDVALUE_PAA),
  15. };
  16. // 猜拳中手势对应的字符串
  17. private static final String[] name = {"石头","剪刀","布"};
  18. // 猜拳中出的手势价值
  19. private int handvalue;
  20. private Hand(int handvalue) {
  21. this.handvalue = handvalue;
  22. }
  23. // 根据手势获取对应实例
  24. public static Hand getHand(int handvalue) {
  25. return hand[handvalue];
  26. }
  27. // 如果 this 赢了 h则返回 true
  28. public boolean isStrongerThan (Hand h) {
  29. return fight(h) == 1;
  30. }
  31. // 如果 this 输给了 h 返回 true
  32. public boolean isWeakerThan (Hand h) {
  33. return fight(h) == -1;
  34. }
  35. // 计分 平 0 胜 1 负 -1
  36. private int fight(Hand h) {
  37. if (this == h) {
  38. return 0;
  39. } else if ((this.handvalue + 1) % 3 == h.handvalue) {
  40. return 1;
  41. } else {
  42. return -1;
  43. }
  44. }
  45. // 转换手势对应的字符串
  46. public String toString () {
  47. return name[handvalue];
  48. }
  49. }

Strategy 接口

  1. package strategy;
  2. /**
  3. * 定义了猜拳策略的抽象方法的接口
  4. */
  5. public interface Strategy {
  6. // 获取下一局要出的手势
  7. public abstract Hand nextHand();
  8. // 学习 ”上一局的手势是否获胜了“,获胜则调用 study(true),stury(false),为下一次的 nextHand 做出依据
  9. public abstract void study(boolean win);
  10. }

WinningStrategy 类

  1. package strategy;
  2. import java.util.Random;
  3. /**
  4. * 第一种策略
  5. */
  6. public class WinningStrategy implements Strategy{
  7. private Random random; // 随机数生成
  8. private boolean won = false; // 保存上一次的输赢
  9. private Hand preHand; // 保存上一次出手势的值
  10. public WinningStrategy(int seed) {
  11. random = new Random(seed);
  12. }
  13. public Hand nextHand() {
  14. if (!won) {
  15. preHand = Hand.getHand(random.nextInt(3));
  16. }
  17. return preHand;
  18. }
  19. public void study(boolean win) {
  20. won = win;
  21. }
  22. }

ProbStrategy 类

  1. package strategy;
  2. import java.util.Random;
  3. /**
  4. * 第二种策略
  5. * 它会记录之前出的手势,和输赢,来算出下次该出的手势
  6. */
  7. public class ProbStrategy implements Strategy{
  8. private Random random;
  9. private int preHandValue = 0;
  10. private int currentHandValue = 0;
  11. // history[上一局出的手势][这一局出的手势] ,是一张表,根据过去的胜负进行概率计算
  12. private int[][] history = {
  13. {1,1,1,},
  14. {1,1,1,},
  15. {1,1,1,},
  16. };
  17. public ProbStrategy(int seed) {
  18. random = new Random(seed);
  19. }
  20. public Hand nextHand() {
  21. int bet = random.nextInt(getSum(currentHandValue));
  22. int handvalue = 0;
  23. if (bet < history[currentHandValue][0]) {
  24. handvalue = 0;
  25. } else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {
  26. handvalue = 1;
  27. } else {
  28. handvalue = 2;
  29. }
  30. preHandValue = currentHandValue;
  31. currentHandValue = handvalue;
  32. return Hand.getHand(handvalue);
  33. }
  34. private int getSum(int hv) {
  35. int sum = 0;
  36. for (int i = 0; i < 3; i++) {
  37. sum+=history[hv][i];
  38. }
  39. return sum;
  40. }
  41. /**
  42. * 根据 nextHand 方法返回手势的胜负结果来更新 history 字段中的值
  43. * @param win
  44. */
  45. public void study(boolean win) {
  46. if (win) {
  47. history[preHandValue][currentHandValue]++;
  48. } else {
  49. history[preHandValue][(currentHandValue + 1) % 3]++;
  50. history[preHandValue][(currentHandValue + 2) % 3]++;
  51. }
  52. }
  53. }

Player 类

  1. package strategy;
  2. /**
  3. * 猜拳游戏的选手类
  4. */
  5. public class Player {
  6. private String name;
  7. private Strategy strategy;
  8. private int wincount;
  9. private int losecount;
  10. private int gamecount;
  11. // 赋予名字和策略
  12. public Player(String name, Strategy strategy) {
  13. this.name = name;
  14. this.strategy = strategy;
  15. }
  16. // 策略决定下一局要出的手势
  17. public Hand nextHand() {
  18. return strategy.nextHand();
  19. }
  20. // 胜
  21. public void win() {
  22. strategy.study(true);
  23. wincount++;
  24. gamecount++;
  25. }
  26. // 负
  27. public void lose() {
  28. strategy.study(false);
  29. losecount++;
  30. gamecount++;
  31. }
  32. // 平
  33. public void even() {
  34. gamecount++;
  35. }
  36. @Override
  37. public String toString() {
  38. return "Player{" +
  39. "name='" + name + '\'' +
  40. ", strategy=" + strategy +
  41. ", wincount=" + wincount +
  42. ", losecount=" + losecount +
  43. ", gamecount=" + gamecount +
  44. '}';
  45. }
  46. }

Main

  1. package strategy;
  2. /**
  3. * 创建两个 Player 分别使用不同的策略进行猜拳游戏
  4. */
  5. public class Main {
  6. public static void main(String[] args) {
  7. int seed1 = 1001;
  8. int seed2 = 1002;
  9. Player player1 = new Player("coco",new WinningStrategy(seed1));
  10. Player player2 = new Player("Gorit",new ProbStrategy(seed2));
  11. for (int i = 0; i < 1000; i++) {
  12. Hand nextHand1 = player1.nextHand();
  13. Hand nextHand2 = player2.nextHand();
  14. if (nextHand1.isStrongerThan(nextHand2)) {
  15. System.out.println("Winner!!!" + player1);
  16. player1.win();
  17. player2.lose();
  18. } else if (nextHand2.isStrongerThan(nextHand1)) {
  19. System.out.println("Winner!!!"+player2);
  20. player2.win();
  21. player1.lose();
  22. } else {
  23. System.out.println("Even....");
  24. player1.even();
  25. player2.even();
  26. }
  27. }
  28. System.out.println("Total result");
  29. System.out.println(player1.toString());
  30. System.out.println(player2.toString());
  31. }
  32. }

运行结果
image.png

10.3 Strategy 模式中登场角色

  • Strategy(策略)

Strategy 角色决定实现策略所必须的接口。在示例程序中,由 Strategy 接口扮演此角色

  • ConcreteStrategy(具体的策略)

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

  • Context 上下文

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

10.4 拓展

  1. 使用 Strategy 模式,不必修改接口。只需要修改 ConcreteStrategy 角色,这种使用委托这种弱关系可以很方便地整体替换算法
  2. 程序运行中也可以切换策略,看系统内存,内存低的切换为(速度慢,省内存的策略),反之就可以换成(速度快,耗内存)的策略