作者案:本文介绍的是 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;
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
问题分析
我觉得这有 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;
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
这个看着也没啥大问题的样子,不就是加了个 if 么,但是由于我们改动了这个文件,测试同学问我们需要测试哪些功能,说是测一种枪需要 5 天。
问题来啦,本来说是你增加一种枪, 需要测 5 天,但是现在你说改了这文件,上下可能有些局部变量共享的,或者有些方法可能改了入参的值,这些有负作用的方法被调用啦,所以可能狙击枪也得测一测,可能手枪也得测一测。
测试同学崩了,本来 5 天的工作量,搞成了 5 * 6 天,一个月都在测枪。
QQ6lKoM" id="hdf2c2dd-2QQ6lKoM">初步尝试解决
我们先定义好一个基础类,解决一下可读性问题和重复性问题。
定义一个基础武器类:
/**
* 抽象的枪
*/
public abstract class Weapon {
/**
* 发现敌人
*/
protected void findEnemy() {
System.out.println("发现敌人");
}
/**
* 开枪前的动作
*/
protected abstract void preAction();
/**
* 开枪
*/
protected abstract void shoot();
/**
* 整体的动作
*/
public void kill() {
findEnemy();
preAction();
shoot();
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
逐个实现武器的具体类、平底锅、冲锋枪、步枪等类如下:
/**
* 平底锅
*/
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("掏出冲锋枪打他");
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
我们的方法就可以改动得更清晰啦。
/**
* 抽象出类代码判断最强武器
*/
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();
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
类图如下:
使用策略模式
上面的代码没有解决最根本的问题,也就是去除 if/else,所用的方法其实就是将 if else 转换为 for,这样的代码后续添加枪就不需要再增加新的类型啦。
我们先定义一个通用的策略模式接口如下:
/**
* 策略模式
*/
public interface Strategy
<T extends AbstractStrategyRequest,
R extends AbstractStrategyResponse> {
/*
* 执行策略
* @param request
* @return
*/
R executeStrategy(T request);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
入参和出参都是基本的抽象类:
/**
* 策略模式抽象入参
*/
public abstract class AbstractStrategyRequest {
}
/**
* 策略模式抽象出参
*/
public abstract class AbstractStrategyResponse {
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
实现一个武器抽象类实现接口:
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;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
其中的 Range 类实现如下:
/**
* 范围类
* @param <T>
*/
@Data
@AllArgsConstructor
public 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;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
依次实现这个抽象武器策略类:
/**
* 平底锅
*/
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);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
定义一个上下文类来对入参进行路由:
/**
* 策略上下文, 用来路由策略
*/
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));
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
最后在主方法里面调用就好啦:
public class App {
public static void main(String[] args) {
StrategyContext.execute(89);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
结果如下:
距离敌人 89
发现敌人
身体站直,心态稳住
掏出冲锋枪打他
类图如下: