• 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策略。

  • 相关阅读:
    js字符串转数组,转对象方法
    react执行yarn eject后配置antd的按需加载
    DOM对象与jquery对象区别
    vscode使用git管理代码
    使用vscode编辑器编辑CPU100%使用率问题
    Java 多态
    1,随机生成一个500m的txt,填充内容为小写的26个字母。生成后,查找abc字符,打印出其数量和位置(越快越好)
    bat 文件
    word2Html
    生成压缩文件
  • 原文地址:https://www.cnblogs.com/SysoCjs/p/10395457.html
Copyright © 2020-2023  润新知