整体地替换算法-Strategy模式

254 阅读2分钟

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);
    }
}

运行结果:

image.png 可以看出,策略二较优于策略1

3、tips

  • 策略模式的可拔插的优良性质在许多框架中有用到,以dubbo为例,dubbo提供了许多负载均衡策略: image.png 而用户想要使用某种策略,只需要在配置文件中配置上即可,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);
}