思变
近期因为重构一个项目,有时间对老代码进行改造,发现很多接口初期设计良好,但是在各种需求的侵袭之下,也变得越加的混乱
,最后就变成了所谓的「垃圾堆」,各种IF-ELSE
满天飞,接手的人叫苦不迭,最后只能闻着恶臭
,捏着鼻子继续往「垃圾堆」里扔垃圾。最后垃圾堆坍塌,重新开新项目
困境
IF-ELSE
代码块多,改造时间短,不宜「伤筋动骨」- 使用策略模式消灭
IF-ELSE
代码块复杂且代码量大,增加许多策略类 - 尽量复用已有的逻辑,不增加新的代码
所得
对Java8
的使用有一段时间了,非常喜欢「Steam」数据处理,感觉根本不是在写Java代码,复杂的代码木有啦,流线型代码写起来,额。。。跑题了,其实最好想说的是Java8
新加的包「Function」,所谓的「函数式」编程,当然对于这种函数式编程是不是弱化版的不说,其实这个包下面有很多好的工具,可以简化我们的工作
注:以下代码只给出核心部分,其他方法请自行查看源码
- 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」是方便大家处理多种入参
❞
- Consumer Function
//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
,两种工具代表策略处理的两种情况,即第一:纯消费(存库等),第二:有生产(过滤数据等),他们就是用来替换策略模式中的策略部分的,放弃编写复杂的策略接口以及策略实现类,简化策略模式的规模,减少代码以及时间成本
- 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
方法就能测试出来的问题,然后大家的问答是:你自己试试呗!所以,程序员一定要会自己试试,所以试试先
- 第一步找一个IF-ELSE块
一个简单权限过滤功能,可以写出3个IF嵌套,逻辑复杂一点呢?
//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的结构,简单三个属性
@Data
public class PowerQuery {
/**
* ALL-全部
* 其他值-无效
*/
private String type;
/**
* 0-内部
* 1-外部
*/
private Integer userType;
/**
* 如果不是ALL角度查看
* 外部用户一次只能查看一个权限
*/
private String powerName;
}
- 盘他
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;
}
- 方式描述
先梳理现有的逻辑,剥离策略的处理逻辑,将各个策略通过Predicate
和Function
组织起来,形成策略模式中的方法簇,最后通过「循环跳出」的方式进行策略「钩子命中」,策略逻辑「运行处理」,代理中其实有很多模仿的痕迹,比如策略使用「List」组织,「循环跳出」进行逻辑处理
总一个结
❝笔者是很
❞喜欢策略模式
的,也会在日常的开发中尝试运用策略模式。在实践的过程中也体会到,策略模式有一定的运用门槛,且感觉策略模式体量较重,每次尝试运用实现,就是一个顶层「策略接口」,加下一大堆策略「实现方法簇」,但是其实平常最需要策略化的其实就是IF-ELSE
,但是一搞就很麻烦,后面接手的兄弟也是一脸懵逼,大呼请容我看一会儿。。。这种极简的策略「贵在简单」,不增加太多的类和接口,简单的转化IF-ELSE
,代码量没有明显的增加,同时也支持了「快速扩展」,如文中所言,确实是思变以后的成果。同时,也要指出这也是笔者「闭门造车」的结果,一家之言难免疏漏,今天分享出来也是希望给大家一些灵感,抛砖引玉、求同存异,有什么想法请在下方留言,大家讨论一下