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则返回 true
public boolean isStrongerThan (Hand h) {
return fight(h) == 1;
}
// 如果 this 输给了 h 返回 true
public boolean isWeakerThan (Hand h) {
return fight(h) == -1;
}
// 计分 平 0 胜 1 负 -1
private 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++;
}
@Override
public 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 角色,这种使用委托这种弱关系可以很方便地整体替换算法
- 程序运行中也可以切换策略,看系统内存,内存低的切换为(速度慢,省内存的策略),反之就可以换成(速度快,耗内存)的策略