StrategyPattern教你秒变神枪手
作者:若采
作者案:本文介绍的是 Strategy Pattern (策略模式)。干货满满,希望阅读后你能有所收获 目的
做一件事情有不同的实现方式,可以将变化的部分和不变的部分剥离开,去除大量的 if/else,提供高扩展性。 例子代码
比如我们想要带妹吃鸡,就要成为一个神枪手。在各种枪战游戏中,有各种不同的枪,我们要根据射程的不同选择不同的枪进行射击。
如果枪的子弹数量都不富裕,我们要用最少的子弹,最合适的方法达到最强伤害,最终大吉大利。
当我们距离对手: 1米以内,使用平底锅(想我当时三级头三级甲,手持 AKM,满血满状态,三级包里药包无数,到了决赛圈被平底锅堵在墙角打死啦 ); 100 米左右,使用冲锋枪; 超过 1000 米,使用狙击枪(对于我这样的小菜鸡,基本流程是开一枪没打中,暴露位置,被别人一狙打死...囧)。 /** * 面条式代码判断最强武器 */ public class NoodlesKillProcessor { /** * 根据距离判断最好的武器击杀对手 * @param distance */ @BadSmell public static void killByDistance(int distance) { if(distance < 0) { throw new RuntimeException("距离咋还能是负数呢?"); } if(distance >= 0 && distance < 1) { System.out.println("发现敌人"); System.out.println("两步快速走过去"); System.out.println("掏出平底锅呼他"); return; } if(distance >= 1 && distance < 10) { System.out.println("发现敌人"); System.out.println("快速走过去"); System.out.println("掏出手枪打他"); return; } if(distance >= 10 && distance < 100) { System.out.println("发现敌人"); System.out.println("身体站直, 心态稳住"); System.out.println("掏出冲锋枪打他"); return; } if(distance >= 100 && distance < 1000) { System.out.println("发现敌人"); System.out.println("身体蹲下降低后坐力"); System.out.println("掏出步枪"); System.out.println("打开 3 倍镜"); System.out.println("开枪射击"); return; } if(distance >= 1000) { System.out.println("发现敌人"); System.out.println("趴在草丛里苟着"); System.out.println("掏出狙击枪"); System.out.println("打开 8 倍镜"); System.out.println("开枪射击"); return; } } }问题分析
我觉得这有 3 个问题,具体分析如下:
01 可读性问题
我看这么多 if/else 语句,里面的 sout 语句目前三四行也还好,如果我们有上百行的语句,里面也有很多 if/else,这样都不知道下个主 if 跑哪去啦
02 重复性问题
全都需要发现敌人,如果发现敌人是个成百上千行代码,就很麻烦啦。
03 可维护性问题
如果这时候我们新增了一种枪,比如是霰弹枪,适用 10 到 20 的时候使用,这时候我们就需要在加一个 if 语句如下: /** * 面条式代码判断最强武器 */ public class NoodlesKillProcessor { /** * 根据距离判断最好的武器击杀对手 * @param distance */ @BadSmell public static void killByDistance(int distance) { if(distance < 0) { throw new RuntimeException("距离咋还能是负数呢?"); } if(distance >= 0 && distance < 1) { System.out.println("发现敌人"); System.out.println("两步快速走过去"); System.out.println("掏出平底锅呼他"); return; } if(distance >= 1 && distance < 10) { System.out.println("发现敌人"); System.out.println("快速走过去"); System.out.println("掏出手枪打他"); return; } if(distance >= 10 && distance < 20) { System.out.println("发现敌人"); System.out.println("身体站直, 瞄准"); System.out.println("打一枪算一枪"); return; } if(distance >= 20 && distance < 100) { System.out.println("发现敌人"); System.out.println("身体站直, 心态稳住"); System.out.println("掏出冲锋枪打他"); return; } if(distance >= 100 && distance < 1000) { System.out.println("发现敌人"); System.out.println("身体蹲下降低后坐力"); System.out.println("掏出步枪"); System.out.println("打开 3 倍镜"); System.out.println("开枪射击"); return; } if(distance >= 1000) { System.out.println("发现敌人"); System.out.println("趴在草丛里苟着"); System.out.println("掏出狙击枪"); System.out.println("打开 8 倍镜"); System.out.println("开枪射击"); return; } } }
这个看着也没啥大问题的样子,不就是加了个 if 么,但是由于我们改动了这个文件,测试同学问我们需要测试哪些功能,说是测一种枪需要 5 天
问题来啦,本来说是你增加一种枪, 需要测 5 天,但是现在你说改了这文件,上下可能有些局部变量共享的,或者有些方法可能改了入参的值,这些有负作用的方法被调用啦,所以可能狙击枪也得测一测,可能手枪也得测一测。
测试同学崩了,本来 5 天的工作量,搞成了 5 * 6 天,一个月都在测枪
初步尝试解决
我们先定义好一个基础类,解决一下可读性问题和重复性问题。
定义一个基础武器类: /** * 抽象的枪 */ public abstract class Weapon { /** * 发现敌人 */ protected void findEnemy() { System.out.println("发现敌人"); } /** * 开枪前的动作 */ protected abstract void preAction(); /** * 开枪 */ protected abstract void shoot(); /** * 整体的动作 */ public void kill() { findEnemy(); preAction(); shoot(); } }
逐个实现武器的具体类、平底锅、冲锋枪、步枪等类如下: /** * 平底锅 */ public class Pan extends Weapon { @Override protected void preAction() { System.out.println("两步快速走过去"); } @Override protected void shoot() { System.out.println("掏出平底锅呼他"); } } /** * 手枪类 */ public class Pistol extends Weapon { @Override protected void preAction() { System.out.println("快速走过去"); } @Override protected void shoot() { System.out.println("掏出手枪打他"); } } /** * 霰弹枪 */ public class Shotgun extends Weapon { @Override protected void preAction() { System.out.println("身体站直, 瞄准"); } @Override protected void shoot() { System.out.println("打一枪算一枪"); } } /** * 狙击枪 */ public class SniperRifle extends Weapon { @Override protected void preAction() { System.out.println("趴在草丛里苟着"); System.out.println("掏出狙击枪"); System.out.println("打开 8 倍镜"); } @Override protected void shoot() { System.out.println("开枪射击"); } } /** * 冲锋枪 */ public class SubmachineGun extends Weapon { @Override protected void preAction() { System.out.println("身体站直, 心态稳住"); } @Override protected void shoot() { System.out.println("掏出冲锋枪打他"); } }
我们的方法就可以改动得更清晰啦 /** * 抽象出类代码判断最强武器 */ public class WeaponKillProcessor { /** * 根据距离判断最好的武器击杀对手 * @param distance */ @BadSmell public static void killByDistance(int distance) { if (distance < 0) { throw new RuntimeException ("距离咋还能是负数呢?"); } Weapon weapon = null; if (distance >= 0 && distance < 1) { weapon = new Pan(); } else if (distance >= 1 && distance < 10) { weapon = new Pistol(); } else if (distance > 10 && distance < 20) { weapon = new Shotgun(); } else if (distance >= 20 && distance < 100) { weapon = new SubmachineGun(); } else if (distance >= 100 && distance < 1000) { weapon = new Rifle(); } else if (distance >= 1000) { weapon = new SniperRifle(); } weapon.kill(); } }
类图如下:
使用策略模式
上面的代码没有解决最根本的问题,也就是去除 if/else,所用的方法其实就是将 if else 转换为 for,这样的代码后续添加枪就不需要再增加新的类型啦。
我们先定义一个通用的策略模式接口如下: /** * 策略模式 */ public interface Strategy { /* * 执行策略 * @param request * @return */ R executeStrategy(T request); }
入参和出参都是基本的抽象类: /** * 策略模式抽象入参 */ public abstract class AbstractStrategyRequest { } /** * 策略模式抽象出参 */ public abstract class AbstractStrategyResponse { }
实现一个武器抽象类实现接口: public abstract class WeaponStrategy implements Strategy { /** * 发现敌人 */ protected void findEnemy() { System.out.println("发现敌人"); } /** * 开枪前的动作 */ protected abstract void preAction(); /** * 开枪 */ protected abstract void shoot(); /** * 获取距离范围 * @return */ protected abstract Range queryDistanceRange(); /** * 整体的动作 */ public void kill() { findEnemy(); preAction(); shoot(); } @Override public AbstractStrategyResponse executeStrategy(WeaponStrategyRequest request) { System.out.println("距离敌人 " + request.getDistance()); kill(); return null; } }
其中的 Range 类实现如下: /** * 范围类 * @param */ @Data @AllArgsConstructor public class Range> { private T start; private T end; public Range(T start, T end) { this.start = start; this.end = end; } private boolean isIncludeStart = true; private boolean isIncludeEnd = false; /** * 判断是否在范围内 * @param target * @return */ public boolean inRange(T target) { if(isIncludeStart) { if(start.compareTo(target) > 0) { return false; } } else { if(start.compareTo(target) >= 0) { return false; } } if(isIncludeEnd) { if(end.compareTo(target) < 0) { return false; } } else { if(end.compareTo(target) <= 0) { return false; } } return true; } }
依次实现这个抽象武器策略类: /** * 平底锅 */ public class PanStrategy extends WeaponStrategy { @Override protected void preAction() { System.out.println("二步快速走过去"); } @Override protected void shoot() { System.out.println("掏出平底锅呼他"); } @Override protected Range queryDistanceRange() { return new Range<>(0, 1); } } /** * 手枪类 */ public class PistolStrategy extends WeaponStrategy { @Override protected void preAction() { System.out.println("快速走过去"); } @Override protected void shoot() { System.out.println("掏出手枪打他"); } @Override protected Range queryDistanceRange() { return new Range<>(1, 10); } } /** * 步枪 */ public class RifleStrategy extends WeaponStrategy { @Override protected void preAction() { System.out.println("身体蹲下降低后坐力"); System.out.println("掏出步枪"); System.out.println("打开 3 倍镜"); } @Override protected void shoot() { System.out.println("开枪射击"); } @Override protected Range queryDistanceRange() { return new Range<>(100, 1000); } } /** * 霰弹枪 */ public class ShotgunStrategy extends WeaponStrategy { @Override protected void preAction() { System.out.println("身体站直, 瞄准"); } @Override protected void shoot() { System.out.println("打一枪算一枪"); } @Override protected Range queryDistanceRange() { return new Range<>(10, 20); } } /** * 狙击枪 */ public class SniperRifleStrategy extends WeaponStrategy { @Override protected void preAction() { System.out.println("趴在草丛里苟着"); System.out.println("掏出狙击枪"); System.out.println("打开 8 倍镜"); } @Override protected void shoot() { System.out.println("开枪射击"); } @Override protected Range queryDistanceRange() { return new Range<>(1000, Integer.MAX_VALUE); } } /** * 冲锋枪 */ public class SubmachineGunStrategy extends WeaponStrategy { @Override protected void preAction() { System.out.println("身体站直, 心态稳住"); } @Override protected void shoot() { System.out.println("掏出冲锋枪打他"); } @Override protected Range queryDistanceRange() { return new Range<>(20, 100); } }
定义一个上下文类来对入参进行路由: /** * 策略上下文, 用来路由策略 */ public class StrategyContext { public static final List WEAPON_STRATEGYS = new ArrayList<>(); static { WEAPON_STRATEGYS.add(new PanStrategy()); WEAPON_STRATEGYS.add(new PistolStrategy()); WEAPON_STRATEGYS.add(new RifleStrategy()); WEAPON_STRATEGYS.add(new ShotgunStrategy()); WEAPON_STRATEGYS.add(new SniperRifleStrategy()); WEAPON_STRATEGYS.add(new SubmachineGunStrategy()); } public static void execute(Integer distance) { WEAPON_STRATEGYS.stream(). filter((weaponStrategy -> { Range integerRange = weaponStrategy.queryDistanceRange(); return integerRange.inRange(distance); })). findAny(). get(). executeStrategy( new WeaponStrategyRequest(distance)); } }
最后在主方法里面调用就好啦: public class App { public static void main(String[] args) { StrategyContext.execute(89); } }
结果如下: 距离敌人 89
发现敌人
身体站直,心态稳住
掏出冲锋枪打他
类图如下:
加入我们
我们来自字节跳动飞书商业应用研发部(Lark Business Applications),目前我们在北京、深圳、上海、武汉、杭州、成都、广州、三亚都设立了办公区域。我们关注的产品领域主要在企业经验管理软件上,包括飞书 OKR、飞书绩效、飞书招聘、飞书人事等 HCM 领域系统,也包括飞书审批、OA、法务、财务、采购、差旅与报销等系统。
点击「链接」,欢迎各位加入我们!
风从草原来休闲巴蜀韵2023川蒙百万人互游文化旅游推广活动来源人民网内蒙古频道为深化内蒙古自治区与山东湖北四川等主力客源地省份的文化和旅游的交流与合作,活跃夏秋季旅游市场,激发游客来内蒙古旅游的积极性,内蒙古自治区文化和旅游厅赴山东省济南
超过50年,珊瑚海风加权路径分布的热带气旋危害的发散文丨波波百谈编辑丨波波百谈起源于珊瑚海的热带气旋(TC)对其周围的沿海地区构成了重大危害。此外,该地区还没有很好地了解TC轨道的不稳定性质。因此,研究人员根据最大风权质心的K均值聚
聚焦GetChat赋能社交网络带你玩出新花样在Web3时代,社交网络仍然扮演着思想市场和公共论坛的角色,Web3革命是文化金融和社会的,这场革命的影响将渗透到网络空间的每一个角落。从这个角度看,革命的前提条件都具备了独立的金
第7章数组数组的引入1练习引入packagemainimportfmtfuncmain()score195score291score339score460score521sumscore1s
张小飞的Java之路字符串(重点)写在前面视频是什么东西,有看文档精彩吗?视频是什么东西,有看文档速度快吗?视频是什么东西,有看文档效率高吗?诸小亮接下来我们就需要学习String张小飞这不就是字符串么,前面也用过
放大镜下的月球,没有水的水星(Mercury)水星是太阳系的八大行星中最小且最靠近太阳的行星。轨道周期是87。9691天,116天左右与地球会合一次,公转速度远远超过太阳系的其它行星。水星是表面昼夜温差最大的行星,大气层极为稀
女性机器人能替代女性吗?女性机器人虽然在功能上可以模仿人类女性的某些特征,但是总体而言,它们并不能真正替代人类女性。首先,无论机器人再真实,它们始终是人工智能,缺少了人类的情感和思维。其次,机器人无法像人
ChatGPT正在被应用科技之颠大家好,我是对什么信息都感兴趣的幕落。前段时间ChatGPT的热潮引起了许多人的思考。人工智能是否能替代我的职业?人工智能真的很厉害吗?人工智能创造的东西是什么样的?近来我
伽师蓝莓快递销往全国各地石榴云新疆日报讯(记者范琼燕报道)伽师县蓝莓大批量上市了,通过电商抖音直播带货,平均每天有近200单由顺丰快递发往全国各地。4月16日下午,新疆顺丰速运有限公司喀什片区伽师业务部快
一篇斯坦福大学的论文引出对AI意识的思考这是一篇斯坦福大学计算心理学教授迈克尔柯辛斯基(MichaelKozinski)不久前发表的一篇论文,标题是TheoryofMindMayHaveSpontaneouslyEmer
足协新领导遭实名举报,名记质疑包庇假球,调查材料上交后没下文国足前主帅李铁被带走调查之后,已经有多名大老虎也落马了,比如杜兆才陈戌源于洪臣陈永亮刘奕黄松王小平,每一个单独拎出来都是响当当的大人物。这也表明了中国足球反腐扫赌的决心,只要涉及到