Strategy被理解为策略模式,可以被理解成编程中的算法。无论什么程序,其目的都是解决问题,而为了解决问题,有需要编写特定的算法,使用Strategy模式可以整体地替换算法的实现部分,能够整体的替换算法,轻松地以不同地方法去解决同一个问题。
针对策略模式设计两种策略。第一种是:如果这句猜拳获胜,下局就出一样地手势。另外一种是:根据上一局地手势概率上计算出下一局地手势。

代码实例:

Hand类:

表示猜拳游戏中“手势”的类。在该类的内部,用int表示所出的手势,0表示石头,1表示剪刀,2表示布。并将值保存在handValue字段中。虽然Hand类会被别的类所使用,但是它并非属于Strategy模式中的角色。

  1. public class Hand {
  2. public static final int HANDVALUE_GUU=0;// 表示石头的值
  3. public static final int HANDVALUE_CHO=1;// 表示剪刀的值
  4. public static final int HANDVALUE_PAA=0;// 表示布的值
  5. public static final Hand[] hand={
  6. new Hand(HANDVALUE_PAA),
  7. new Hand(HANDVALUE_CHO),
  8. new Hand(HANDVALUE_GUU)
  9. };
  10. private static final String[] name = {
  11. "石头","剪刀","布"
  12. };
  13. private int handValue;
  14. public Hand(int handValue) {
  15. this.handValue=handValue;
  16. }
  17. public static Hand getHand(int handValue) {
  18. return hand[handValue];
  19. }
  20. public boolean isStrongerThan(Hand h){
  21. return fight(h)==1;
  22. }
  23. public boolean isWeakerThan(Hand h){
  24. return fight(h)==-1;
  25. }
  26. private int fight(Hand h) {
  27. if(this==h){
  28. return 0;
  29. }else if((this.handValue+1)%3==h.handValue){
  30. return 1;
  31. }else{
  32. return -1;
  33. }
  34. }
  35. @Override
  36. public String toString() {
  37. return "Hand{" +
  38. "handValue=" + handValue +
  39. '}';
  40. }
  41. }

Strategy接口:

定义了猜拳策略的抽象方法的接口,nextHand返回下一轮输出的手势,study是学习本轮手势。

  1. public interface Strategy {
  2. public abstract Hand nextHand();
  3. public abstract void study(boolean win);
  4. }

WinningStrategy类:

Strategy接口的实现类之一。策略为如果上一轮的手势获胜了,则下一局则相同,如果上一轮输了,则下一局随机。

  1. public class WinningStrategy implements Strategy{
  2. private Random random;
  3. private boolean won = false;
  4. private Hand prevHand;
  5. public WinningStrategy(int seed) {
  6. this.random = new Random(seed);
  7. }
  8. @Override
  9. public Hand nextHand() {
  10. if(!won){
  11. prevHand = Hand.getHand(random.nextInt(3));
  12. }
  13. return prevHand;
  14. }
  15. @Override
  16. public void study(boolean win) {
  17. won = win; // 根据输赢情况定义不同学习情况
  18. }
  19. }

ProbStrategy:

另外一种此策略,会根据之前的概率求出这轮应该出的手势。

  1. public class ProbStrategy implements Strategy{
  2. private Random random;
  3. private int prevHandValue = 0;
  4. private int currentHandValue = 0;
  5. private int[][] history = {
  6. {1,1,1},
  7. {1,1,1},
  8. {1,1,1},
  9. };
  10. public ProbStrategy(int seed) {
  11. this.random = new Random(seed);
  12. }
  13. @Override
  14. public Hand nextHand(){
  15. int bet=random.nextInt(getSum(currentHandValue));
  16. int handValue=0;
  17. if(bet<history[currentHandValue][0]){
  18. handValue = 0;
  19. }else if(bet<history[currentHandValue][1]){
  20. handValue = 1;
  21. }else{
  22. handValue = 2;
  23. }
  24. prevHandValue = currentHandValue;
  25. currentHandValue = handValue;
  26. return Hand.getHand(handValue);
  27. }
  28. private int getSum(int hv) {
  29. int sum = 0;
  30. for (int i = 0; i < 3; i++) {
  31. sum +=history[hv][i];
  32. }
  33. return sum;
  34. }
  35. @Override
  36. public void study(boolean win) {
  37. if(win){
  38. history[prevHandValue][currentHandValue]++;
  39. }else {
  40. history[prevHandValue][(currentHandValue+1)%3]++;
  41. history[prevHandValue][(currentHandValue+2)%3]++;
  42. }
  43. }
  44. }

Player类:

猜拳选手实例对象。strategy属性存储策略,在Main中再生成具体策略,与player绑定,来实现策略设计模式。

  1. public class Player {
  2. private String name;
  3. private Strategy strategy;
  4. private int winCount;
  5. private int loseCount;
  6. private int gameCount;
  7. public Player(String name,Strategy strategy) {
  8. this.name = name;
  9. this.strategy = strategy;
  10. }
  11. public Hand nextHand(){
  12. return strategy.nextHand();
  13. }
  14. public void win(){
  15. strategy.study(true);
  16. winCount++;
  17. gameCount++;
  18. }
  19. public void lose(){
  20. strategy.study(false);
  21. loseCount++;
  22. gameCount++;
  23. }
  24. public void even(){
  25. gameCount++;
  26. }
  27. @Override
  28. public String toString() {
  29. return "Player{" +
  30. "name='" + name + '\'' +
  31. ", strategy=" + strategy +
  32. ", winCount=" + winCount +
  33. ", loseCount=" + loseCount +
  34. ", gameCount=" + gameCount +
  35. '}';
  36. }
  37. }

Main类:

  1. public class Main {
  2. public static void main(String[] args) {
  3. if (args.length!=2) {
  4. System.out.println("输入格式错误");
  5. System.exit(0);
  6. }
  7. int seed1 = Integer.parseInt(args[0]);
  8. int seed2 = Integer.parseInt(args[1]);
  9. Player player1 = new Player("Taro", new WinningStrategy(seed1));
  10. Player player2 = new Player("Hano", new ProbStrategy(seed2));
  11. for (int i = 0; i < 10000; i++) {
  12. Hand nextHand1 = player1.nextHand();
  13. Hand nextHand2 = player2.nextHand();
  14. if (nextHand1.isStrongerThan(nextHand2)) {
  15. System.out.println("Winner:"+player1);
  16. player1.win();
  17. player2.lose();
  18. }else if(nextHand1.isWeakerThan(nextHand2)){
  19. System.out.println("Winner:"+player2);
  20. player1.lose();
  21. player2.win();
  22. }else{
  23. System.out.println("Even..");
  24. player1.even();
  25. player2.even();
  26. }
  27. System.out.println("Total result:");
  28. System.out.println(player1.toString());
  29. System.out.println(player2.toString());
  30. }
  31. }
  32. }

Strategy中的登场角色:

Strategy(策略):

Strategy角色负责决定实现策略所必须的接口(API)。

ConcreteStrategy(具体的策略):

ConcreteStrategy角色负责实现Strategy角色的接口,即负责实现具体的策略(战略、方法和算法)

Context(上下文):

负责使用Strategy角色。Context角色保存了ConcreteStrategy的实例对象,并使用ConcreteStrategy角色去实现需求。

拓展:

为什么需要特意编写Strategy角色:

通常编程时算法会被写在具体方法中,Strategy模式却特意将算法与其他部分分离开来,只是定义了与算法相关的接口,然后在程序中以委托的方法来使用算法。
这样看起来程序好像变复杂了,其实不然。例如,当需要通过改善算法来提高算法的处理速度时,如果使用了Strategy模式,就不必修改Strategy角色的接口了,仅仅修改ConcreteStrategy角色即可。而且,使用委托这种弱关联关系可以很方便地整体替换算法。

程序运行中也可以切换策略:

如果使用Strategy模式,在程序运行中也可以切换ConcreteStrategy角色。此外,还可以用某种算法去“验证”另外一种算法。例如:假设要在某个表格计算软件的开发版本中进行复杂的计算。可以使用两种算法,首先时“高速但计算上可能有Bug的算法”和“低速但计算准确的算法”,然后用后者去验算前者的计算结果。

相关的设计模式:

Flyweight模式:

有时会使用Flyweight模式让多个地方可以共用ConcreteStrategy角色。

Abstract Factory模式:

  • 使用Strategy模式可以整体地替换算法。
  • 使用Abstract Factory模式则可以整体地替换具体工厂、零件和产品。

    State模式:

    使用Strategy模式和State模式都可以替换被委托对象,而且它们的类之间的关系也很相似。
    在Streategy模式中,ConcreteStrategy角色时表示算法的类。可以被委托的类都可以是。当然如果没有必要,也可以不替换。
    而在State模式中,ConcreteStrategy角色是表示“状态”的类。每次状态变化时,被委托对象的类都必定会被替换。