一、模式说明
策略模式比较好理解,就是将程序中用到的算法整体的拿出来,并有多个不同版本的算法实现,在程序运行阶段,动态的决定使用哪个算法来解决问题。
举个实际的例子:排序算法的问题,假如我们的程序中需要对数据进行排序,我们知道,不同的算法具有不同的时间复杂度和空间复杂度,因此需要在程序运行时,根据可用内存和数据特征,选用不同的算法(排序策略),这就是策略模式的使用场景之一。再举个例子,负载均衡算法:如果某个服务部署了多个冗余的实例,客户端在向服务端发送请求时,根据负载均衡算法策略,请求可能会被转发到不同的服务提供者实例来处理,如何决定某个请求转发给哪个服务实例呢,最简单的做法就是轮询,顺次将请求转发给每个服务实例进行处理。也可以采用随机方式,或者根据实际硬件环境和业务场景设置特定算法。
二、模式类图
三、程序示例
在下面的演示策略模式的代码示例中,我们模拟猜拳游戏——剪刀石头布,猜拳的策略有两种:如果这次猜拳赢了,则下次还出同样的手势。另一种策略就是根据以前的猜拳结果,选择胜率最高的一种手势。
1、Hand类:表示猜拳游戏中的手势,并非Strategy策略模式中的角色。
package com.designpattern.cn.strategypattern;public class Hand { public static final int HANDVALUE_ROCK = 0; //表示石头 public static final int HANDVALUE_SCISSORS = 1; //表示剪刀 public static final int HANDVALUE_PEPER = 2; //表示布 private static final Hand[] hand = { new Hand(HANDVALUE_ROCK), new Hand(HANDVALUE_SCISSORS), new Hand(HANDVALUE_PEPER) }; 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、Strategy接口:
package com.designpattern.cn.strategypattern;public interface Strategy { public abstract Hand nextHand(); public abstract void study(boolean win);}
3、WinningStrategy类:
package com.designpattern.cn.strategypattern;import java.util.Random;public class WinningStrategy implements Strategy { private Random random; private boolean won = false; //上一局的输赢结果 private Hand prevHand; //上一局的手势 public WinningStrategy(int seed){ random = new Random(seed); } public Hand nextHand(){ if(!won){ prevHand = Hand.getHand(random.nextInt(3)); } return prevHand; } public void study(boolean win){ won = win; }}
4、ProbStrategy类:
package com.designpattern.cn.strategypattern;import java.util.Random;public class ProbStrategy implements Strategy { private Random random; private int prevHandValue = 0; private int currentHandValue = 0; //history[上一局的手势][这一局的手势] 表达式的值越高表示过去的胜率越高 //study方法会根据nextHand方法返回的手势胜负结果更新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][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 : history[hv] ) { sum += i; } return sum; } public void study(boolean win){ if(win){ history[prevHandValue][currentHandValue]++; }else{ history[prevHandValue][(currentHandValue+1)%3]++; history[prevHandValue][(currentHandValue+2)%3]++; } }}
5、Player类:
package com.designpattern.cn.strategypattern;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.designpattern.cn.strategypattern;import java.util.Random;public class Main { public static void main(String[] args){ int seed1 = ((new Random()).nextInt(500))*(1+(new Random()).nextInt(500)); int seed2 = seed1 * (new Random()).nextInt(500); Player player1 = new Player("Taro", new WinningStrategy(seed1)); Player player2 = new Player("Hana", new ProbStrategy(seed2)); for(int i = 0; i < 100000; 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()); }}
四、Strategy策略模式中的角色
- Strategy策略:负责定义实现策略必须的接口方法
- ConcreteStrategy具体的策略:实现Strategy角色的接口,如程序中的WinningStrategy和ProbStrategy
- Context上下文:负责使用Strategy策略,如示例程序中的player。
五、相关的设计模式
- Flyweight享元模式:通过使用享元模式,让多个地方共用ConcreteStrategy角色;
- Abstract Factory抽象工厂:策略模式整体替换算法,抽象工厂整体替换具体的工厂,零件和产品;
- State状态模式:状态模式和策略模式都可以替换被委托对象,而且类之间的关系也相似,只是两种模式目的不同。strategy策略模式替换被委托对象的类;状态模式中,每次状态发生变化时,被委托的对象必定会被替换。