一天一个设计模式——Strategy策略模式

一、模式说明

  策略模式比较好理解,就是将程序中用到的算法整体的拿出来,并有多个不同版本的算法实现,在程序运行阶段,动态的决定使用哪个算法来解决问题。

  举个实际的例子:排序算法的问题,假如我们的程序中需要对数据进行排序,我们知道,不同的算法具有不同的时间复杂度和空间复杂度,因此需要在程序运行时,根据可用内存和数据特征,选用不同的算法(排序策略),这就是策略模式的使用场景之一。再举个例子,负载均衡算法:如果某个服务部署了多个冗余的实例,客户端在向服务端发送请求时,根据负载均衡算法策略,请求可能会被转发到不同的服务提供者实例来处理,如何决定某个请求转发给哪个服务实例呢,最简单的做法就是轮询,顺次将请求转发给每个服务实例进行处理。也可以采用随机方式,或者根据实际硬件环境和业务场景设置特定算法。

二、模式类图

一天一个设计模式——Strategy策略模式

三、程序示例

  在下面的演示策略模式的代码示例中,我们模拟猜拳游戏——剪刀石头布,猜拳的策略有两种:如果这次猜拳赢了,则下次还出同样的手势。另一种策略就是根据以前的猜拳结果,选择胜率最高的一种手势。

1、Hand类:表示猜拳游戏中的手势,并非Strategy策略模式中的角色。

一天一个设计模式——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];
    }
}
View Code

2、Strategy接口:

一天一个设计模式——Strategy策略模式
package com.designpattern.cn.strategypattern;

public interface Strategy {
    public abstract Hand nextHand();
    public abstract void study(boolean win);
}
View Code

3、WinningStrategy类:

一天一个设计模式——Strategy策略模式
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;
    }
}
View Code

4、ProbStrategy类:

一天一个设计模式——Strategy策略模式
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]++;
        }
    }
}
View Code

5、Player类:

一天一个设计模式——Strategy策略模式
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" + "]";
    }
}
View Code

6、Main类与运行结果:

一天一个设计模式——Strategy策略模式
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());
    }
}
View Code

一天一个设计模式——Strategy策略模式

四、Strategy策略模式中的角色

  • Strategy策略:负责定义实现策略必须的接口方法
  • ConcreteStrategy具体的策略:实现Strategy角色的接口,如程序中的WinningStrategy和ProbStrategy
  • Context上下文:负责使用Strategy策略,如示例程序中的player。

五、相关的设计模式

  • Flyweight享元模式:通过使用享元模式,让多个地方共用ConcreteStrategy角色;
  • Abstract Factory抽象工厂:策略模式整体替换算法,抽象工厂整体替换具体的工厂,零件和产品;
  • State状态模式:状态模式和策略模式都可以替换被委托对象,而且类之间的关系也相似,只是两种模式目的不同。strategy策略模式替换被委托对象的类;状态模式中,每次状态发生变化时,被委托的对象必定会被替换。

 

上一篇:没搞明白的最大生成树


下一篇:Admob与Audience Network通用的基础广告政策