网易云音乐-王菲-我和我的祖国
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.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.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.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.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