GOF设计模式——Strategy模式
一、什么是Strategy模式?
Strategy,意思是“策略”的意思。使用Strategy模式设计的代码,就自带一种逻辑判断在里面,可以整体的替换算法的实现部分,或者说跟机器学习有相似之处。
二、Strategy模式思想
Context类:里面定义了Strategy类型属性,负责使用Strategy接口,使用了委托,实际上是调用Strategy的实现类的具体方法;
Strategy接口:负责决定实现策略所必须的接口;
ConcreteStrategy类:实现了Strategy接口的策略方法。
三、Strategy模式示例
这里有一个猜拳游戏,现在设定有两种出拳策略,第一种是“如果这局猜拳获胜,那么下一局也出同样的手势”;第二种是“根据上一次的手势概率计算下一局的手势”。
UML图:
1、Hand类:Hand类并不是Strategy模式的角色,这里只是为了方便,特意编写了一个表示“手势”的类,使用final和static修饰的int类型表示所出的手势,其中0表示石头,1表示剪刀,2表示布,并将值保存在handvalue里面。isStrongerTha和isWeakerThan用于判断猜拳的结果。
package com.cjs.Strategy;
public class Hand {
private final static int HANDVALUE_GUU = 0;
private final static int HANDVALUE_CHO = 1;
private final static int HANDVALUE_PAA = 2;
private int handvalue;
private Hand(int handvalue) {
this.handvalue = handvalue;
}
public static Hand getHand(int handvalue) {
return hand[handvalue];
}
private static final Hand[] hand = {new Hand(HANDVALUE_CHO), new Hand(HANDVALUE_GUU), new Hand(HANDVALUE_PAA)};
private final static String[] name = {"石头", "剪刀", "布"};
public boolean isStrongerThan(Hand hand) {
return fight(hand) == 1;
}
public boolean isWeakerThan(Hand hand) {
return fight(hand) == -1;
}
private int fight(Hand hand) {
if (this == hand) {
return 0;
} else if ((this.handvalue + 1) % 3 == hand.handvalue) {
return 1;
} else {
return -1;
}
}
public String toString() {
return name[handvalue];
}
}
2、Strategy接口:定义了猜拳的抽象方法的接口,newtHand用于获取下一局要出的手势,study方法是学习“上一局的手势是否获胜”。
package com.cjs.Strategy;
public interface Strategy {
public abstract Hand nextHand();
public abstract void study(boolean win);
}
3、WinningStrategy类:实现了Strategy接口,这个类设定是属于猜拳的第一种策略,就是如果这局赢了,下一局就继续使用同样的手势。
package com.cjs.Strategy;
import java.util.Random;
public class WinningStrategy implements Strategy {
private Random random;
private boolean won = false;
private Hand preHand;
public WinningStrategy(int seed) {
this.random = new Random(seed);
}
@Override
public Hand nextHand() {
//如果上一局输了,就随机出拳
if (!won) {
preHand = Hand.getHand(random.nextInt(3));
}
return preHand;
}
@Override
public void study(boolean win) {
won = win;
}
}
4、ProbStrategy类:实现了Strategy接口,这个类设定是属于第二种策略,会根据之前猜拳的出拳概率来决定i下一局应该使用哪一种手势。
package com.cjs.Strategy;
import java.util.Random;
public class ProbStrategy implements Strategy {
private Random random;
private int preHandValue = 0;
private int currentHandValue = 0;
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 = 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);
}
@Override
public void study(boolean win) {
if (win) {
history[preHandValue][currentHandValue]++;
} else {
//如果没有获胜,意味着其他两种手势的获胜次数要+1
history[preHandValue][(currentHandValue+1)%3]++;
history[preHandValue][(currentHandValue+2)%3]++;
}
}
//计算基于上一局手势时的三种手势胜出次数总和
public int getSum(int hv) {
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += history[hv][i];
}
return sum;
}
}
history[][]字段是一个表,用于根据过去的胜负来计算概率,它是一个二维数组,每个元素可以表示为:history[上一局的手势][下一局的手势]。假设上一局出的是石头,用0去表示,则上一局出拳头的history有三种可能,分别是history[0][0],history[0][1]和history[0][2],每个值代表胜出次数:两局都出石头时胜出的次数,两局分别出石头、剪刀时胜出的次数和两局分别出石头、布时胜出的次数。
假如history[0][0]=3,history[0][1]=5,history[0][2]=7,下一局出石头、剪刀和布的概率比就是3:5:7。
5、Player类:表示进行猜拳游戏的选手类。
package com.cjs.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++;
}
public String toString() {
return "[" + name + ":" + gameCount + " games, " + winCount + " win, " + loseCount + " lose " + "]";
}
}
6、Main类
package com.cjs.Strategy;
import java.util.Random;
public class Main {
public static void main(String[] args) {
Random random = new Random(System.currentTimeMillis());
int seed1 = random.nextInt(3);
int seed2 = random.nextInt(3);
Player player1 = new Player("Tony", new WinningStrategy(seed1));
Player player2 = new Player("Jimmy", new ProbStrategy(seed2));
for (int i = 0; i < 500; i++) {
Hand nextHand1 = player1.nextHand();
Hand nextHand2 = player2.nextHand();
if (nextHand1.isStrongerThan(nextHand2)) {
System.out.println("Winner is : " + player1);
player1.win();
player2.lose();
} else if (nextHand1.isWeakerThan(nextHand2)) {
System.out.println("Winner is : " + 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());
}
}
运行多次,会有不同的结果出现,输出结果:
四、Strategy模式的优点
貌似使用Strategy模式会使得程序变得复杂,其实不然。例如,当我们想要通改善算法来提高算法 的处理速度时,如果使用了Strategy模式,就不必要修改Strategy角色的接口,仅仅修改ConcreteStrategy角色类的实现即可。而且,使用委托这种弱关联关系,使得整体替换算法更加方便。在很多游戏里面选择关卡难度时,就是选择不同的AI策略。