- APP神圣官网 > 计划生活 > 正文
神枪手教你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<T extends AbstractStrategyRequest,R extends AbstractStrategyResponse> { / 执行策略 @param request @return / R executeStrategy(T request);}
入参和出参都是基本的抽象类:/ 策略模式抽象入参 /public abstract class AbstractStrategyRequest {}/ 策略模式抽象出参 /public abstract class AbstractStrategyResponse {}
实现一个武器抽象类实现接口:public abstract class WeaponStrategy implements Strategy<WeaponStrategyRequest,AbstractStrategyResponse> { / 发现敌人 / protected void findEnemy() { System.out.println("发现敌人"); } / 开枪前的动作 / protected abstract void preAction(); / 开枪 / protected abstract void shoot(); / 获取距离范围 @return / protected abstract Range<Integer> queryDistanceRange(); / 整体的动作 / public void kill() { findEnemy(); preAction(); shoot(); } @Override public AbstractStrategyResponse executeStrategy(WeaponStrategyRequest request) { System.out.println("距离敌人 " + request.getDistance()); kill(); return null; }}
其中的 Range 类实现如下:/ 范围类 @param <T> /@Data@AllArgsConstructorpublic class Range<T extends Comparable<T>> { 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> queryDistanceRange() { return new Range<>(20, 100); }}
定义一个上下文类来对入参进行路由:/ 策略上下文, 用来路由策略 /public class StrategyContext { public static final List<WeaponStrategy> 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<Integer> 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、法务、财务、采购、差旅与报销等系统点击「链接」,欢迎各位加入我们
联系我们
在线咨询:
0 评论