GOF设计模式——Strategy模式

一、什么是Strategy模式?

        Strategy,意思是“策略”的意思。使用Strategy模式设计的代码,就自带一种逻辑判断在里面,可以整体的替换算法的实现部分,或者说跟机器学习有相似之处。

二、Strategy模式思想

Context类:里面定义了Strategy类型属性,负责使用Strategy接口,使用了委托,实际上是调用Strategy的实现类的具体方法;

Strategy接口:负责决定实现策略所必须的接口;

ConcreteStrategy类:实现了Strategy接口的策略方法。

三、Strategy模式示例

        这里有一个猜拳游戏,现在设定有两种出拳策略,第一种是“如果这局猜拳获胜,那么下一局也出同样的手势”;第二种是“根据上一次的手势概率计算下一局的手势”。

UML图:

1、Hand类:Hand类并不是Strategy模式的角色,这里只是为了方便,特意编写了一个表示“手势”的类,使用final和static修饰的int类型表示所出的手势,其中0表示石头,1表示剪刀,2表示布,并将值保存在handvalue里面。isStrongerTha和isWeakerThan用于判断猜拳的结果。

package com.cjs.Strategy;
 
public class Hand {
    private final static int HANDVALUE_GUU = 0;
    private final static int HANDVALUE_CHO = 1;
    private final static int HANDVALUE_PAA = 2;
 
    private int handvalue;
 
    private Hand(int handvalue) {
        this.handvalue = handvalue;
    }
 
    public static Hand getHand(int handvalue) {
        return hand[handvalue];
    }
 
    private static final Hand[] hand = {new Hand(HANDVALUE_CHO), new Hand(HANDVALUE_GUU), new Hand(HANDVALUE_PAA)};
 
    private final static String[] name = {"石头", "剪刀", "布"};
 
    public boolean isStrongerThan(Hand hand) {
        return fight(hand) == 1;
 
    }
 
    public boolean isWeakerThan(Hand hand) {
        return fight(hand) == -1;
    }
 
    private int fight(Hand hand) {
        if (this == hand) {
            return 0;
        } else if ((this.handvalue + 1) % 3 == hand.handvalue) {
            return 1;
        } else {
            return -1;
        }
    }
 
    public String toString() {
        return name[handvalue];
    }
}

2、Strategy接口:定义了猜拳的抽象方法的接口,newtHand用于获取下一局要出的手势,study方法是学习“上一局的手势是否获胜”。

package com.cjs.Strategy;
 
public interface Strategy {
    public abstract Hand nextHand();
 
    public abstract void study(boolean win);
}

3、WinningStrategy类:实现了Strategy接口,这个类设定是属于猜拳的第一种策略,就是如果这局赢了,下一局就继续使用同样的手势。

package com.cjs.Strategy;
 
import java.util.Random;
 
public class WinningStrategy implements Strategy {
    private Random random;
    private boolean won = false;
    private Hand preHand;
 
    public WinningStrategy(int seed) {
        this.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;
    }
}

4、ProbStrategy类:实现了Strategy接口,这个类设定是属于第二种策略,会根据之前猜拳的出拳概率来决定i下一局应该使用哪一种手势。

package com.cjs.Strategy;
 
import java.util.Random;
 
public class ProbStrategy implements Strategy {
    private Random random;
    private int preHandValue = 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;
        }
        //当前手势“变成”上一局手势
        preHandValue = currentHandValue;
        //计算后手势“变成”当前手势
        currentHandValue = handvalue;
        return Hand.getHand(handvalue);
    }
 
    @Override
    public void study(boolean win) {
        if (win) {
            history[preHandValue][currentHandValue]++;
        } else {
            //如果没有获胜,意味着其他两种手势的获胜次数要+1
            history[preHandValue][(currentHandValue+1)%3]++;
            history[preHandValue][(currentHandValue+2)%3]++;
        }
    }
 
//计算基于上一局手势时的三种手势胜出次数总和
    public int getSum(int hv) {
        int sum = 0;
        for (int i = 0; i < 3; i++) {
            sum += history[hv][i];
        }
        return sum;
    }
}

        history[][]字段是一个表,用于根据过去的胜负来计算概率,它是一个二维数组,每个元素可以表示为:history[上一局的手势][下一局的手势]。假设上一局出的是石头,用0去表示,则上一局出拳头的history有三种可能,分别是history[0][0],history[0][1]和history[0][2],每个值代表胜出次数:两局都出石头时胜出的次数,两局分别出石头、剪刀时胜出的次数和两局分别出石头、布时胜出的次数。

        假如history[0][0]=3,history[0][1]=5,history[0][2]=7,下一局出石头、剪刀和布的概率比就是3:5:7。

5、Player类:表示进行猜拳游戏的选手类。

package com.cjs.Strategy;
 
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.cjs.Strategy;
 
import java.util.Random;
 
public class Main {
    public static void main(String[] args) {
        Random random = new Random(System.currentTimeMillis());
        int seed1 = random.nextInt(3);
        int seed2 = random.nextInt(3);
        Player player1 = new Player("Tony", new WinningStrategy(seed1));
        Player player2 = new Player("Jimmy", new ProbStrategy(seed2));
 
        for (int i = 0; i < 500; i++) {
            Hand nextHand1 = player1.nextHand();
            Hand nextHand2 = player2.nextHand();
            if (nextHand1.isStrongerThan(nextHand2)) {
                System.out.println("Winner is : " + player1);
                player1.win();
                player2.lose();
            } else if (nextHand1.isWeakerThan(nextHand2)) {
                System.out.println("Winner is : " + player2);
                player1.lose();
                player2.win();
            } 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模式会使得程序变得复杂,其实不然。例如,当我们想要通改善算法来提高算法 的处理速度时,如果使用了Strategy模式,就不必要修改Strategy角色的接口,仅仅修改ConcreteStrategy角色类的实现即可。而且,使用委托这种弱关联关系,使得整体替换算法更加方便。在很多游戏里面选择关卡难度时,就是选择不同的AI策略。

原文地址:https://www.cnblogs.com/SysoCjs/p/10395457.html