极简策略模式

2,704 阅读9分钟

思变

近期因为重构一个项目,有时间对老代码进行改造,发现很多接口初期设计良好,但是在各种需求的侵袭之下,也变得越加的混乱,最后就变成了所谓的垃圾堆,各种IF-ELSE满天飞,接手的人叫苦不迭,最后只能闻着恶臭,捏着鼻子继续往垃圾堆里扔垃圾。最后垃圾堆坍塌,重新开新项目

困境

  1. IF-ELSE代码块多,改造时间短,不宜伤筋动骨
  2. 使用策略模式消灭IF-ELSE代码块复杂且代码量大,增加许多策略类
  3. 尽量复用已有的逻辑,不增加新的代码

所得

Java8的使用有一段时间了,非常喜欢Steam数据处理,感觉根本不是在写Java代码,复杂的代码木有啦,流线型代码写起来,额。。。跑题了,其实最好想说的是Java8新加的包Function,所谓的函数式编程,当然对于这种函数式编程是不是弱化版的不说,其实这个包下面有很多好的工具,可以简化我们的工作

注:以下代码只给出核心部分,其他方法请自行查看源码

  1. Predicate BiPredicate

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
@FunctionalInterface
public interface BiPredicate<T, U> {
    boolean test(T t, U u);
}

第一个工具是Predicate,他的作用是什么呢?顾名思义,他提供一种判断逻辑,他可以替换策略模式钩子方法,钩子方法就是决定使用何种策略去处理当前逻辑,当然钩子可以是由工厂方法提供,但是极简模式的策略选择将钩子置于策略内部 PS:这边给出两个Predicate是方便大家处理多种入参

  1. Consumer Function
//Consumer
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
@FunctionalInterface
public interface BiConsumer<TU> {
    void accept(T t, U u);
}
//Function
@FunctionalInterface
public interface Function<TR> {
    apply(T t);
}
@FunctionalInterface
public interface BiFunction<TUR> {
    apply(T t, U u);
}

第二个工具是Consumer或者Function,两种工具代表策略处理的两种情况,即第一:纯消费(存库等),第二:有生产(过滤数据等),他们就是用来替换策略模式中的策略部分的,放弃编写复杂的策略接口以及策略实现类,简化策略模式的规模,减少代码以及时间成本

  1. Pair
// 笔者使用org.springframework.data.util下的Pair工具
// 因为不需要引入其他依赖
// PS: Pair的实现很多,可以自行选择食用
public final class Pair<S, T> {
    @NonNull
    private final S first;
    @NonNull
    private final T second;

    public static <S, T> Pair<S, T> of(S first, T second) {
        return new Pair(first, second);
    }

    public S getFirst() {
        return this.first;
    }

    public T getSecond() {
        return this.second;
    }
}

很明显,Pair是用来存放策略的,使得钩子策略逻辑组装形成策略处理单元,说白了就是一个工具容器,这个容器种类很多,可以自己找一个自己喜欢的,笔者只是就近挑选

试试

经常有人在群里问一些其实自己写个Main方法就能测试出来的问题,然后大家的问答是:你自己试试呗!所以,程序员一定要会自己试试,所以试试先

  1. 第一步找一个IF-ELSE块

一个简单权限过滤功能,可以写出3个IF嵌套,逻辑复杂一点呢?

//PowerQuery结构在下方
public List<StringfilterPowerByQuery(List<String> powers, PowerQuery query) {
        if ("ALL".equalsIgnoreCase(query.getType())) {
            //内部用户和外部用户ALL返回全部
            return powers;
        }
        if (new Integer(0).equals(query.getUserType())) {
            //内部用户
            if (StringUtils.isNotBlank(query.getPowerName())) {
                //内部用户可以查看 类型  权限 (以PowerName为前缀)
                return powers.stream()
                             .filter(s -> StringUtils.startsWithIgnoreCase(s, query.getPowerName()))
                             .collect(Collectors.toList());
            }
            //内部用户其他情况
            return powers;
        } else {
            //非ALL的情况下,外部用户一次只能查看一种权限的数据
            return powers.stream()
                         .filter(s -> StringUtils.equals(query.getPowerName(), s))
                         .collect(Collectors.toList());
        }
    }

PowerQuery的结构,简单三个属性

@Data
public class PowerQuery {
    /**
     * ALL-全部
     * 其他值-无效
     */
    private String type;

    /**
     * 0-内部
     * 1-外部
     */
    private Integer userType;

    /**
     * 如果不是ALL角度查看
     * 外部用户一次只能查看一个权限
     */
    private String powerName;
}

  1. 盘他
public List<StringfilterPowerByStrategy(List<String> allPowers, PowerQuery powerQuery) {
        //这个例子中策略模式有明显的链式的规则
        //但是使用List也可以很好地反应这种规则
        //类似Spring的DispatchServlet中的各种Resolver等也是List组织的
        List<Pair<Predicate<PowerQuery>, BiFunction<List<String>, PowerQueryList<String>>>> chains = new ArrayList<>();
        //ALL的逻辑
        chains.add(Pair.of(query -> "ALL".equalsIgnoreCase(query.getType()), (powers, query) -> powers));
        //这里将外部用户的逻辑提到上部
        chains.add(Pair.of(query -> new Integer(1).equals(query.getUserType()), (powers, query) -> powers));
        //内部用户且PowerName有值
        chains.add(Pair.of(query -> new Integer(0).equals(query.getUserType()) && StringUtils.isNotBlank(query.getPowerName()),
                           (powers, query) -> powers.stream()
                                                    .filter(s -> StringUtils.startsWithIgnoreCase(s, query.getPowerName()))
                                                    .collect(Collectors.toList())));
        //最后增加一个收尾的策略 其他情况统一返回原全量权限
        chains.add(Pair.of(query -> true, (powers, query) -> powers));
        //使用策略List
        for (Pair<Predicate<PowerQuery>, BiFunction<List<String>, PowerQuery, List<String>>> chain : chains) {
            if (chain.getFirst().test(powerQuery)) {
                return chain.getSecond().apply(allPowers, powerQuery);
            }
        }
        //这个逻辑是不会走的
        return allPowers;
    }
  1. 方式描述

先梳理现有的逻辑,剥离策略的处理逻辑,将各个策略通过PredicateFunction组织起来,形成策略模式中的方法簇,最后通过循环跳出的方式进行策略钩子命中,策略逻辑运行处理,代理中其实有很多模仿的痕迹,比如策略使用List组织,循环跳出进行逻辑处理

总一个结

笔者是很喜欢策略模式的,也会在日常的开发中尝试运用策略模式。在实践的过程中也体会到,策略模式有一定的运用门槛,且感觉策略模式体量较重,每次尝试运用实现,就是一个顶层策略接口,加下一大堆策略实现方法簇,但是其实平常最需要策略化的其实就是IF-ELSE,但是一搞就很麻烦,后面接手的兄弟也是一脸懵逼,大呼请容我看一会儿。。。这种极简的策略贵在简单,不增加太多的类和接口,简单的转化IF-ELSE,代码量没有明显的增加,同时也支持了快速扩展,如文中所言,确实是思变以后的成果。同时,也要指出这也是笔者闭门造车的结果,一家之言难免疏漏,今天分享出来也是希望给大家一些灵感,抛砖引玉、求同存异,有什么想法请在下方留言,大家讨论一下