zoukankan      html  css  js  c++  java
  • 策略模式


    网易云音乐-王菲-我和我的祖国

    1.引言

    1.1 LOL四大渡劫

    "天雷"
    在2016年的世界总决赛上,ROX战队对战G2战队,ROX的上单选手Semb使用凯南,在队友被先开团的情况下,Semb反手开团使用大招打出爆炸伤害,将比赛直接终结。
    "地火"
    2015年的世界总决赛上,EDG战队对战IG战队,两方在小龙处交战,EDG中单Pawn将军使用船长,事先将爆破桶藏在河道处的草丛中,一波四连桶的操作打出了爆炸伤害,将IG的队员全部打残。
    "湮灭"
    2017年的LPL赛区比赛,OMG战队对战EDG战队,双方争夺视野,EDG将OMG上单鳄鱼的复活甲打出,想做进一步的追击。这时OMG的ADC选手Smlz使用大嘴反身闪现一喷五获取四杀。
    "飞升"
    2017年的LCK赛区比赛,SKT战队对战KT战队。KT战队刚刚打完大龙出来接战,SKT的中单Faker使用发条,蓝Buff处闪现大招,大到四人。直接送KT四人上天。

        回顾下名场面,了解英雄联盟的人大都听说过这些经典团战。后来还引申了IG上单TheShy的"剑来",RNG上单Letme的"山崩"等。能被评入渡劫,可以说是靠选手使用英雄以一己之力打开了局面。这里大都是一次关键团战里,靠以上选手灵性发挥,闪现+技能,打出第一波完美控制或伤害,队友跟上输出拿下团战的。
        英雄联盟的英雄众多,每个英雄拥有四种英雄技能。一场关键团里有十个英雄在短时间内,打出数十个技能,但是很多英雄技能是有相同的控制效果,比如击飞,击退,石化等;这些释放技能的算法如果对于每个英雄都单独去用算法实现,那么方法肯会存在冗余,且不易维护。
        冗余伪代码展示:

    public class HeroContext {
        public void releaseSkill(Hero hero) {
            if (heroType == 1) {
                if (skillType == 1) {
                  // 算法:类型为1的英雄释放1技能
                } else if (skillType == 2) {
                  // 算法:类型为1的英雄释放2技能
                } else if (skillType == 3) {
                  // 算法:类型为1的英雄释放3技能
                }
            } else if(heroType == 2) {
                // 算法
                ...
            } else if(heroType == 3) {
                // 算法
                ...
            } else {...}
        }
    }
    

    1.2 策略模式是一种对象行为型模式

        策略模式在设计模式中相对更容易理解,在客户端程序中包含多种算法会使得代码复杂化,扩展性差。如果对于某个程序业务存在大量的可行算法,都维护在同一方法块,问题会更严重,是不可取的。
        我们把不同的算法抽离为单独的类去实现,对于不同的实现方式去调用不同的策略实现类即可。同时该模式提供抽象策略类来保证多个策略实现的一致性。即对于客户端来说,可以使用同一个入口来调用不同的策略,不同的策略可以相互替换。这样使得算法独立于客户端,可以单独维护,且可任意扩展策略实现。
        简单来说还是面向对象开发而不是面向过程开发。将算法作为具体策略对象的方法。客户端通过调用不同的策略实现对象的方法,代替把多个算法写在一段程序中。

    2. 为什么引入策略模式

        策略模式就是对不同的算法单独封装为策略类,把算法分隔开,使不同的算法可以通过不同的策略类来实现,调用者不用关注其具体实现;环境类会提供统一调用方法,指定不同的策略即可执行不同的策略内部的算法。
        策略模式提供环境类 StrategyContext,环境类由抽象策略类 AbstractStrategy 聚合而成,环境类提供统一方法 algorithm() 调用具体策略类的方法 algorithm() 。具体策略类来负责内部算法 algorithm() 的实现。

    3. 什么情况下使用策略模式

    • 对于不同的行为类型要使用不同的具体实现时,可以使用策略模式优化;
    • 对于分支语句内代码块较复杂时,可以使用策略模式优化,可用于优化质量较差的if else程序。

    4.策略模式实现

    4.1 实现方式:抽象类+继承

    4.1.1 角色
        传统策略模式的角色包含:环境类,抽象策略类,具体策略类。
        具体策略类继承抽象策略类,不同的具体策略实现不同的策略算法。环境类和抽象策略类之间是聚合关系。
    4.1.2 类图

    图4-1.抽象类+继承方式策略模式类图

    4.1.3 代码实现

    • AbstractStrategy.java
    /** 抽象策略类 */
    public abstract class AbstractStrategy {
        public abstract void algorithm();
    }
    
    • ConcreteStrategyA.java
    /** 具体策略类 */
    public class ConcreteStrategyA extends AbstractStrategy  {
        /* 抽象方法实现算法 */
        public void algorithm() {
            // 算法A
            System.out.println("策略实现方法");
        }
    }
    
    • Context.java
    /** 策略环境类 */
    public class Context {
        private AbstractStrategy strategy;
        /* 抽象方法实现算法 */
        public void setStrategy(AbstractStrategy  strategy) {
            this.strategy = strategy;
        }
        /* 环境类统一实现策略方法 */
        public void algorithm() {
            strategy.algorithm();
        }
    }
    

    4.2 实现方式:接口 + 组合

    4.2.1 角色
        接口 + 组合实现方式下角色包含:环境类,策略接口,接口实现。
        策略实现类和策略接口之间是实现关系,实现具体的策略算法。环境类和策略接口之间是聚合关系。
        两种方式的区别在于使用接口+组合的方式替换继承,Java开发规范建议使用组合替换继承,来避免继承的误用,当然上面的示例并没有误用。继承与组合的本质区别在于,继承是"Is-a"关系,组合是"Has-a"关系。从类图中也可以看到环境类Context 用的是聚合(组合)关系。
    4.2.2 类图

    图4-2.接口+组合方式策略模式类图

    4.2.3 代码实现

    • IStrategy.java
    /** 抽象策略类 */
    public interface IStrategy {
        public abstract void algorithm();
    }
    
    • ConcreteStrategyAImpl.java
    /** 具体策略类 */
    public class ConcreteStrategyAImpl implements IStrategy {
        /* 抽象方法实现算法 */
        public void algorithm() {
            // 算法A
            System.out.println("策略实现方法");
        }
    }
    
    • Context.java
    /** 策略环境类 */
    public class Context {
        private IStrategy strategy;
        /* 抽象方法实现算法 */
        public void setStrategy(IStrategy strategy) {
            this.strategy = strategy;
        }
        /* 环境类统一实现策略方法 */
        public void algorithm() {
            strategy.algorithm();
        }
    }
    

    5. 策略模式实例

    5.1 演示实例说明

        王者荣耀已经接近四周年了,英雄越出越多,技能越来越炫;不过技能效果还是沿(抄)用(袭)英雄联盟的技能效果。常见的硬控包括击飞,眩晕,石化,冰冻,嘲讽等,软控包括减速,弱化等。硬控适合开团,软控适合团战留人;一波完美团战的打响大都是由一波控制开启的。
        这里把英雄的控制技能抽象为对象的行为,该对象作为抽象策略接口,提供释放技能的方法;对不同的控制效果使用不同的策略实现类,实现类负责完成控制效果实现。

    5.2 演示实例类图

    图5-1.演示实例类图

    5.3 演示实例代码

    (1) 新建技能效果策略接口

    • HeroSkillStrategy .java
    /**
     * @className: HeroSkillStrategy
     * @description: (抽象策略)抽象英雄技能
     * 此处使用接口+组合的方式替换抽象类+继承;
     * 组合遵循“has a”的思想,继承遵循“is a”的思想;
     * 抽象类与具体类之间有严格的继承关系,遵循“is a”的思想;
     * @author: niaonao
     * @date: 2019/9/26
     **/
    public interface HeroSkillStrategy {
    
        /** 发动技能 */
        void releaseSkill();
    }
    

    (2) 技能释放环境类
        创建技能效果触发环境类HeroContext.java

    • HeroContext.java
    /**
     * @className: HeroContext
     * @description: 策略使用者环境
     * @author: niaonao
     * @date: 2019/9/26
     **/
    public class HeroContext {
    
        /** 技能策略 */
        private HeroSkillStrategy heroSkillStrategy;
        public void setSkill(HeroSkillStrategy heroSkillStrategy) {
            this.heroSkillStrategy = heroSkillStrategy;
        };
        /**
         * 释放技能
         */
        public void releaseSkill() {
            heroSkillStrategy.releaseSkill();
        }
    }
    

    (3) 控制技能实现类

    • 新建减速效果实现类DecelerateStrategy.java
    • 新建冰冻效果实现类FreezeStrategy.java
    • 新建石化效果实现类PetrifyStrategy.java
    • 新建击飞效果实现类SmiteStrategy.java
    • 新建眩晕效果实现类StunStrategy.java
    • 新建压制效果实现类SuppressStrategy.java
    • 新建嘲讽效果实现类TauntStrategy.java
    import lombok.extern.slf4j.Slf4j;
    /**
     * @className: DecelerateStrategy
     * @description: (具体策略)减速技能
     * @author: niaonao
     * @date: 2019/9/26
     **/
    @Slf4j
    public class DecelerateStrategy implements HeroSkillStrategy {
        /** 减速策略实现 */
        @Override
        public void releaseSkill() {
            // 策略具体算法
            log.info("减速留住敌方前排,双C伤害很高,前排被击杀了,这波大龙要放掉了!
    ");
        }
    }
    
    /** (具体策略)冰冻技能 */
    import lombok.extern.slf4j.Slf4j;
    @Slf4j
    public class FreezeStrategy implements HeroSkillStrategy {
        /** 冰冻策略实现 */
        @Override
        public void releaseSkill() {
            // 策略具体算法
            log.info(" --> 冰冻前排,双C伤害很高,前排被打残,这波大龙要放掉了!
    ");
        }
    }
    
    /** (具体策略)石化技能 */
    import lombok.extern.slf4j.Slf4j;
    @Slf4j
    public class PetrifyStrategy implements HeroSkillStrategy {
        /** 石化策略实现 */
        @Override
        public void releaseSkill() {
            // 策略具体算法
            log.info(" --> 石化鲁班,ad瞬间融化,敌方战术性放掉暴君!
    ");
        }
    }
    
    /** @description: (具体策略)击飞技能 */
    import lombok.extern.slf4j.Slf4j;
    @Slf4j
    public class SmiteStrategy implements HeroSkillStrategy {
        /** 击飞策略实现 */
        @Override
        public void releaseSkill() {
            // 策略具体算法
            log.info(" --> 击飞五人,双C跟上输出,打出团灭,顺势拿下大龙!
    ");
        }
    }
    
    /** 眩晕技能 */
    import lombok.extern.slf4j.Slf4j;
    @Slf4j
    public class StunStrategy implements HeroSkillStrategy {
        /** 眩晕策略实现 */
        @Override
        public void releaseSkill() {
            // 策略具体算法
            log.info(" --> 眩晕三人,一波完美高地团战,攻破敌方水晶!
    ");
        }
    }
    
    /** 压制技能 */
    import lombok.extern.slf4j.Slf4j;
    @Slf4j
    public class SuppressStrategy implements HeroSkillStrategy {
        /** 压制策略实现 */
        @Override
        public void releaseSkill() {
            // 策略具体算法
            log.info(" --> 压制一诺赵云,直接控到死,一诺没有办法啊!
    ");
        }
    }
    
    /** 嘲讽或沉默技能 */
    import lombok.extern.slf4j.Slf4j;
    @Slf4j
    public class TauntStrategy implements HeroSkillStrategy {
        /** 嘲讽策略实现 */
        @Override
        public void releaseSkill() {
            // 策略具体算法
            log.info(" --> 闪现嘲讽姜子牙,瞬间融化,这波可以直接上高地!
    ");
        }
    }
    

    (4) 模拟客户端程序
        创建策略环境类测试,模拟客户端使用策略。

    import lombok.extern.slf4j.Slf4j;
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * @className: HeroSkillTests
     * @description: 测试
     * @author: niaonao
     * @date: 2019/9/26
     **/
    @Slf4j
    public class HeroSkillTests {
    
        private static List<String> SuppressHeroList = Arrays.asList("东皇太一", "张良");
        private static List<String> TauntHeroList = Arrays.asList("白起", "司马懿", "花木兰");
        private static List<String> SmiteHeroList = Arrays.asList("牛魔", "苏烈", "孙策", "达摩", "刘禅");
    
        public static void main(String args[]) {
            /*
             * (1)客户端知道调用者时,直接使用相应的策略
             */
            CowDemonEnterArena();
            /*
             * (2)不知道调用者时,结合if else 或switch 使用相应的策略;
             * 所以策略模式也可做分支语句的优化
             */
            heroEnterArena("张良");
            heroEnterArena("白起");
            heroEnterArena("达摩");
            heroEnterArena("西施");
        }
    
        /**
         * 牛魔进场
         */
        private static void CowDemonEnterArena() {
            // 牛魔二技能击飞效果
            HeroContext heroContext = new HeroContext();
            String heroName = "牛魔";
            log.info("{}闪现给到大招,二技能进场", heroName);
            heroContext.setSkill(new SmiteStrategy());
            heroContext.releaseSkill();
        }
    
        /**
         * 英雄进场
         */
        private static void heroEnterArena(String heroName) {
            // 默认减速效果
            HeroSkillStrategy heroSkillStrategy = new DecelerateStrategy();
            if (SuppressHeroList.contains(heroName)) {
                heroSkillStrategy = new SuppressStrategy();
            } else if (TauntHeroList.contains(heroName)) {
                heroSkillStrategy = new TauntStrategy();
            } else if (SmiteHeroList.contains(heroName)) {
                heroSkillStrategy = new SmiteStrategy();
            } else {
                log.warn("新英雄 {} 体验服公测中,请维护技能控制策略。", heroName);
            }
            log.info("{}进场", heroName);
            HeroContext heroContext = new HeroContext();
            heroContext.setSkill(heroSkillStrategy);
            heroContext.releaseSkill();
        }
    }
    

        测试结果如下:

    图5-2.演示程序测试结果图

    5.4 完善环境类,减少客户端代码

        我们可以在环境类提供按类型适配具体实现策略的方法,把客户端的分支判断交给环境类维护,客户端可以直接传已知的类型即可,具体策略的选择交个环境类处理。使得客户端代码更简洁,代码复用性更好,客户端使用具体策略的场景比较多的情况下,这个情况更明显。

    • 环境类 HeroSkillContext.java
    import lombok.extern.slf4j.Slf4j;
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * @className: HeroSkillContext
     * @description: 策略使用者环境
     * @author: niaonao
     * @date: 2019/9/26
     **/
    @Slf4j
    public class HeroSkillContext {
    
        /** 技能策略 */
        private HeroSkillStrategy heroSkillStrategy;
        /** 客户端设置具体策略 */
        public void setSkill(HeroSkillStrategy heroSkillStrategy) {
            this.heroSkillStrategy = heroSkillStrategy;
        };
    
        private static List<String> SuppressHeroList = Arrays.asList("东皇太一", "张良");
        private static List<String> TauntHeroList = Arrays.asList("白起", "司马懿", "花木兰");
        private static List<String> SmiteHeroList = Arrays.asList("牛魔", "苏烈", "孙策", "达摩", "刘禅");
        /** 根据类型(此处为名称)设置合适的策略 */
        public void setSkill(String heroName) {
            if (SuppressHeroList.contains(heroName)) {
                this.heroSkillStrategy = new SuppressStrategy();
            } else if (TauntHeroList.contains(heroName)) {
                this.heroSkillStrategy = new TauntStrategy();
            } else if (SmiteHeroList.contains(heroName)) {
                this.heroSkillStrategy = new SmiteStrategy();
            } else {
                this.heroSkillStrategy = new DecelerateStrategy();
                log.warn("新英雄 {} 体验服公测中,请维护技能控制策略。", heroName);
            }
        }
        /**
         * 释放技能
         */
        public void releaseSkill() {
            heroSkillStrategy.releaseSkill();
        }
    }
    
    • 模拟客户端程序 HeroSkillContextTests.java
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * @className: HeroSkillTests
     * @description: 环境类提供按类型选择具体策略的方法,减少客户端频繁操作
     * @author: niaonao
     * @date: 2019/9/26
     **/
    @Slf4j
    public class HeroSkillContextTests {
    
        public static void main(String args[]) {
            /*
             * (1)客户端知道调用者时,直接使用相应的策略
             */
            CowDemonEnterArena();
            /*
             * (2)不知道调用者时,结合if else 或switch 使用相应的策略;
             * 所以策略模式也可做分支语句的优化
             */
            heroEnterArena("张良");
            heroEnterArena("白起");
            heroEnterArena("达摩");
            heroEnterArena("西施");
        }
    
        /**
         * 牛魔进场
         */
        private static void CowDemonEnterArena() {
            // 牛魔二技能击飞效果
            HeroSkillContext heroContext = new HeroSkillContext();
            String heroName = "牛魔";
            log.info("{}闪现给到大招,二技能进场", heroName);
            heroContext.setSkill(new SmiteStrategy());
            heroContext.releaseSkill();
        }
    
        /**
         * 英雄进场
         */
        private static void heroEnterArena(String heroName) {
            // 默认减速效果
            log.info("{}进场", heroName);
            HeroSkillContext heroContext = new HeroSkillContext();
            heroContext.setSkill(heroName);
            heroContext.releaseSkill();
        }
    }
    

    The End, Thanks

  • 相关阅读:
    java 基本功
    orale问题汇总
    redis 集群
    centos7 之 systemctl
    redis 常见问题记录
    docker问题汇总
    sql plus 导出建表语句
    linux 添加删除用户
    mysql常用配置
    SAS 查看临时数据集
  • 原文地址:https://www.cnblogs.com/niaonao/p/11593297.html
Copyright © 2011-2022 走看看