极简策略模式

思变

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

困境

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

所得

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

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

  1. Predicate BiPredicate
1
2
3
4
5
6
7
8
9
复制代码
@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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码//Consumer
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
}
//Function
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}


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

  1. Pair
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码// 笔者使用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嵌套,逻辑复杂一点呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码//PowerQuery结构在下方
public List<String> filterPowerByQuery(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的结构,简单三个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码@Data
public class PowerQuery {
    /**
     * ALL-全部
     * 其他值-无效
     */
    private String type;

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

    /**
     * 如果不是ALL角度查看
     * 外部用户一次只能查看一个权限
     */
    private String powerName;
}
  1. 盘他
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
复制代码public List<String> filterPowerByStrategy(List<String> allPowers, PowerQuery powerQuery) {
        //这个例子中策略模式有明显的链式的规则
        //但是使用List也可以很好地反应这种规则
        //类似Spring的DispatchServlet中的各种Resolver等也是List组织的
        List<Pair<Predicate<PowerQuery>, BiFunction<List<String>, PowerQuery, List<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,代码量没有明显的增加,同时也支持了「快速扩展」,如文中所言,确实是思变以后的成果。同时,也要指出这也是笔者「闭门造车」的结果,一家之言难免疏漏,今天分享出来也是希望给大家一些灵感,抛砖引玉、求同存异,有什么想法请在下方留言,大家讨论一下

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%