14

Strategy Pattern 教你秒变“神枪手”

 3 years ago
source link: https://www.51cto.com/article/716881.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Strategy Pattern 教你秒变“神枪手”

原创 精选
作者: 字节跳动技术团队 2022-08-22 14:48:59
本文介绍的是 Strategy Pattern (策略模式)。干货满满,希望阅读后你能有所收获~

​作者案:本文介绍的是 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
@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;


    }
}

依次实现这个抽象武器策略类:

/**
 * 平底锅
 */
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

身体站直,心态稳住

掏出冲锋枪打他

类图如下:

图片
责任编辑:未丽燕 来源: 字节跳动技术团队

</article


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK