策略模式 - 这波不亏


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

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伤害很高,前排被击杀了,这波大龙要放掉了!\n");
    }
}

/** (具体策略)冰冻技能 */
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FreezeStrategy implements HeroSkillStrategy {
    /** 冰冻策略实现 */
    @Override
    public void releaseSkill() {
        // 策略具体算法
        log.info(" --> 冰冻前排,双C伤害很高,前排被打残,这波大龙要放掉了!\n");
    }
}

/** (具体策略)石化技能 */
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PetrifyStrategy implements HeroSkillStrategy {
    /** 石化策略实现 */
    @Override
    public void releaseSkill() {
        // 策略具体算法
        log.info(" --> 石化鲁班,ad瞬间融化,敌方战术性放掉暴君!\n");
    }
}

/** @description: (具体策略)击飞技能 */
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SmiteStrategy implements HeroSkillStrategy {
    /** 击飞策略实现 */
    @Override
    public void releaseSkill() {
        // 策略具体算法
        log.info(" --> 击飞五人,双C跟上输出,打出团灭,顺势拿下大龙!\n");
    }
}

/** 眩晕技能 */
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class StunStrategy implements HeroSkillStrategy {
    /** 眩晕策略实现 */
    @Override
    public void releaseSkill() {
        // 策略具体算法
        log.info(" --> 眩晕三人,一波完美高地团战,攻破敌方水晶!\n");
    }
}

/** 压制技能 */
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SuppressStrategy implements HeroSkillStrategy {
    /** 压制策略实现 */
    @Override
    public void releaseSkill() {
        // 策略具体算法
        log.info(" --> 压制一诺赵云,直接控到死,一诺没有办法啊!\n");
    }
}

/** 嘲讽或沉默技能 */
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TauntStrategy implements HeroSkillStrategy {
    /** 嘲讽策略实现 */
    @Override
    public void releaseSkill() {
        // 策略具体算法
        log.info(" --> 闪现嘲讽姜子牙,瞬间融化,这波可以直接上高地!\n");
    }
}

(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

posted @ 2019-09-26 17:42  niaonao  阅读(380)  评论(0编辑  收藏  举报