一、什么是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策略。