Author:Gorit
Date:2021年2月16日
Refer:《图解设计模式》
10.1 Strategy 模式
“Strategy”指的是 “策略” 模式,指的是与敌军对垒时的行军作战的方法。在代码的领域中,我们可以将其理解为“算法”
设计程序的目的,为了解决特定的问题,而为了解决问题,我们就要编写特定的算法。使用 Strategy 可以整体地替换算法的实现部分。能够整体地替换算法,能让我们轻松地以不同的算法解决同一类问题,这就是 策略模式
10.2 示例程序
10.2.1 程序功能
我们要实现一段使用了 Strategy 模式的示例程序。程序要实现的功能是让电脑玩“猜拳”游戏。
我们考虑两种猜拳策略(设计猜拳的算法规则):
- “如果这局猜拳获胜,那么下一句继续出一样的手势”
- “根据上一局的手势从概率上计算下一局的手势”
10.2.2 类和接口一览表
| 名字 | 说明 |
|---|---|
| Hand | 表示猜拳游戏中的“手势” |
| Strategy | 表示猜拳游戏中策略的类 |
| WinningStrategy | 表示上述策略 1 |
| ProbStrategy | 表示上述策略 2 |
| Player | 表示进行猜拳游戏的选手的类 |
| Main | 测试程序行为的类 |
10.2.3 程序具体实现
Hand 类
package strategy;/*** 表示程序猜拳手势的类。在该类的内部* int 表示手势,0 石头,1 表示剪刀,2 表示布,将值保存在 handValue 字段中* 我们需要三个 Hand 实例,保存在*/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 = { // 表示猜拳中 3 种手势的实例new Hand(HANDVALUE_GUU),new Hand(HANDVALUE_CHO),new Hand(HANDVALUE_PAA),};// 猜拳中手势对应的字符串private static final String[] name = {"石头","剪刀","布"};// 猜拳中出的手势价值private int handvalue;private Hand(int handvalue) {this.handvalue = handvalue;}// 根据手势获取对应实例public static Hand getHand(int handvalue) {return hand[handvalue];}// 如果 this 赢了 h则返回 truepublic boolean isStrongerThan (Hand h) {return fight(h) == 1;}// 如果 this 输给了 h 返回 truepublic boolean isWeakerThan (Hand h) {return fight(h) == -1;}// 计分 平 0 胜 1 负 -1private int fight(Hand h) {if (this == h) {return 0;} else if ((this.handvalue + 1) % 3 == h.handvalue) {return 1;} else {return -1;}}// 转换手势对应的字符串public String toString () {return name[handvalue];}}
Strategy 接口
package strategy;/*** 定义了猜拳策略的抽象方法的接口*/public interface Strategy {// 获取下一局要出的手势public abstract Hand nextHand();// 学习 ”上一局的手势是否获胜了“,获胜则调用 study(true),stury(false),为下一次的 nextHand 做出依据public abstract void study(boolean win);}
WinningStrategy 类
package strategy;import java.util.Random;/*** 第一种策略*/public class WinningStrategy implements Strategy{private Random random; // 随机数生成private boolean won = false; // 保存上一次的输赢private Hand preHand; // 保存上一次出手势的值public WinningStrategy(int seed) {random = new Random(seed);}public Hand nextHand() {if (!won) {preHand = Hand.getHand(random.nextInt(3));}return preHand;}public void study(boolean win) {won = win;}}
ProbStrategy 类
package strategy;import java.util.Random;/*** 第二种策略* 它会记录之前出的手势,和输赢,来算出下次该出的手势*/public class ProbStrategy implements Strategy{private Random random;private int preHandValue = 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);}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;}preHandValue = currentHandValue;currentHandValue = handvalue;return Hand.getHand(handvalue);}private int getSum(int hv) {int sum = 0;for (int i = 0; i < 3; i++) {sum+=history[hv][i];}return sum;}/*** 根据 nextHand 方法返回手势的胜负结果来更新 history 字段中的值* @param win*/public void study(boolean win) {if (win) {history[preHandValue][currentHandValue]++;} else {history[preHandValue][(currentHandValue + 1) % 3]++;history[preHandValue][(currentHandValue + 2) % 3]++;}}}
Player 类
package strategy;/*** 猜拳游戏的选手类*/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++;}@Overridepublic String toString() {return "Player{" +"name='" + name + '\'' +", strategy=" + strategy +", wincount=" + wincount +", losecount=" + losecount +", gamecount=" + gamecount +'}';}}
Main
package strategy;/*** 创建两个 Player 分别使用不同的策略进行猜拳游戏*/public class Main {public static void main(String[] args) {int seed1 = 1001;int seed2 = 1002;Player player1 = new Player("coco",new WinningStrategy(seed1));Player player2 = new Player("Gorit",new ProbStrategy(seed2));for (int i = 0; i < 1000; 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);player2.win();player1.lose();} else {System.out.println("Even....");player1.even();player2.even();}}System.out.println("Total result");System.out.println(player1.toString());System.out.println(player2.toString());}}
运行结果
10.3 Strategy 模式中登场角色
- Strategy(策略)
Strategy 角色决定实现策略所必须的接口。在示例程序中,由 Strategy 接口扮演此角色
- ConcreteStrategy(具体的策略)
ConcreteStrategy 角色负责实现 Strategy 角色的接口,即负责实现具体的策略(战略、方向、算法)。示例程序中由 WinningStrategy 类 和 ProbStrategy 类扮演侧角色
- Context 上下文
负责使用 Strategy 角色,Context 中保存了 ConcreteStrategy 角色的实例,并使用 ConcreteStrategy 角色去实现需求(总之,还是要调用 Strategy 角色的 API)。由 Player 扮演此角色
10.4 拓展
- 使用 Strategy 模式,不必修改接口。只需要修改 ConcreteStrategy 角色,这种使用委托这种弱关系可以很方便地整体替换算法
- 程序运行中也可以切换策略,看系统内存,内存低的切换为(速度慢,省内存的策略),反之就可以换成(速度快,耗内存)的策略
