1、引入
自古中国就有许多兵法家写下著名的战略,如《孙子兵法》、《三十六计》。而对于程序而言,在面对不同情况时,应该有着不同的应对策略(strategy)。
而Strategy模式便是这一要求的实现者,提供了可拔插式的策略供给选择。对于某一特定对象,赋予其不同的策略对象,它便能在相同情况下作出不同的行为。
2、简例
以石头剪刀布为例,可以包括但不限于以下两种策略:
- 策略1:只要赢了,就检持先前的出法,直到输了,再随机更换。
- 策略2:统计先前的战绩,在前一手的出发下,下一手不同的出法获胜的概率各是什么。按照各个出法的权重来按权随机。
2.1、Hand类
Hand类是生活中石头剪刀布三种出法的抽象:
public class Hand {
public static final int HAND_VALUE_GUU=0;
public static final int HAND_VALUE_CHO=1;
public static final int HAND_VALUE_PAA=2;
public static final Hand[] hand={
new Hand(HAND_VALUE_GUU),
new Hand(HAND_VALUE_CHO),
new Hand(HAND_VALUE_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];
}
public boolean isStrongerThan(Hand h) {
return fight(h)==1;
}
public boolean isWeakerThan(Hand h) {
return fight(h)==-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];
}
}
2.2、策略
首先各个策略需要有统一的接口暴露:
public interface Strategy {
public abstract Hand nextHand();
public abstract void study(boolean win);
}
策略1的实现:
public class WinningStrategy implements Strategy{
private Random random;
private boolean won=false;
private Hand preHand;
public WinningStrategy(int seed) {
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;
}
}
策略2的实现:
public class ProbStrategy implements Strategy{
private Random random;
private int prevHandValue=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;
}
prevHandValue=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;
}
@Override
public void study(boolean win) {
if (win) {
history[prevHandValue][currentHandValue]++;
} else {
history[prevHandValue][(currentHandValue+1)%3]++;
history[prevHandValue][(currentHandValue+2)%3]++;
}
}
}
2.4、玩家Player类
将玩石头剪刀布的玩家抽象出来:
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 + ''' +
", winCount=" + winCount +
", loseCount=" + loseCount +
", gameCount=" + gameCount +
'}';
}
}
2.5、测试
初始化两个玩家,让他们采取不同的策略对局10000次:
public class Main {
public static void main(String[] args) {
int seed1=103;
int seed2=321;
Player player1 = new Player("tom", new WinningStrategy(seed1));
Player player2 = new Player("jerry", new ProbStrategy(seed2));
for (int i = 0; i < 10000; i++) {
Hand hand1 = player1.nextHand();
Hand hand2 = player2.nextHand();
if (hand1.isStrongerThan(hand2)) {
player1.win();
player2.lose();
} else if (hand1.isWeakerThan(hand2)) {
player1.lose();
player2.win();
} else {
player1.even();
player2.even();
}
}
System.out.println("result:");
System.out.println(player1);
System.out.println(player2);
}
}
运行结果:
可以看出,策略二较优于策略1
3、tips
- 策略模式的可拔插的优良性质在许多框架中有用到,以dubbo为例,dubbo提供了许多负载均衡策略:
而用户想要使用某种策略,只需要在配置文件中配置上即可,dubbo在启动时就会自动采取该策略进行负载均衡。
public Result invoke(Invocation invocation) throws RpcException {
this.checkWhetherDestroyed();
LoadBalance loadbalance = null;
List<Invoker<T>> invokers = this.list(invocation);
if (invokers != null && !invokers.isEmpty()) {
loadbalance = (LoadBalance)ExtensionLoader.getExtensionLoader(LoadBalance.class).
getExtension(((Invoker)invokers.get(0)).getUrl().
getMethodParameter(invocation.getMethodName(), "loadbalance", "random"));
}
RpcUtils.attachInvocationIdIfAsync(this.getUrl(), invocation);
return this.doInvoke(invocation, invokers, loadbalance);
}