在业务中砰然坠地的设计

热乎的夏天到了,硕士毕业已经 4 年,21 年的阿布,还在为了华为的 offer 努力的刷着慕课(虽然最后也没去),学习一条条的设计模式,单例、工厂、责任链、策略 等等。等草草的工作了这几年发现,课程上面优雅新奇不起眼的设计模式,与实际的业务场景结合起来,在工作中 砰然坠地 之后,有的是非常普通的代码,有的是了不起的、让人印象深刻的设计。

在中国,任何超脱飞扬的思想都会砰然坠地的,现实的引力太沉重了。

资源加载厂

当一条链路里面,涉及到 Rpc、Redis、特征、实验 等一系列资源调用,而且,这些资源调用都是固定的,即资源调用没有前置条件,只与调用的场景相关,你会怎么做呢?阿布当时是新建了一个统一资源模块,接口类有两个方法,getResource() 为资源获取,getScene() 为资源标识,实现该接口的为几个抽象模板类。

  • IResource
    • AbstractFeatureResource
      • FeedbackEventFeatureResource

当需要把统一资源加载模块提供出去的时候,第一种方式是把接口提供出去,如:

java 复制代码
Map<String, IResource> sceneResources = new HashMap<>();

调用方需要根据当前场景获取映射的资源标识,通过资源标识获取资源类,然后通过 getResource() 方法加载资源,如果一条链路里面需要多次调用资源,那就需要多次的执行上述动作。

更为简单的是,提供一个通用的资源加载类以及资源加载方法,如:

java 复制代码
public class ResouceLoader {
    
    Map<String, IResource> sceneResources = new HashMap<>();

    public ResourceResult load(ResourceContext context, String scene) {
        // todo
    }
    
}

方法内部封装获取资源标识、获取并解析资源的过程,调用方通过 ResouceLoader#load 方法就可以获取到本场景对应的资源结果,当然,作为一个稳定的资源模块,方法内部封装的打点、监控、降级也必不可少。

这就是工厂的能力,它内聚了全部的资源生产线,并提供通用的结果生产能力,让外部可以更加简单的完成调用。但笔者也想说,不是任何的接口都需要创建一个工厂,大多数场景下,直接把一系列的接口注入到调用方更为简单合适,有时候,减少不必要类的数量也很重要。

责任链与策略的选择

过滤,在实际的业务中非常常见,拿到一堆的商品,通过一些属性进行过滤,得到符合的商品,或者是拿到一堆的用户,通过用户的特征进行过滤,得到符合的用户。如果让你来进行过滤模块的设计,你会怎么做,会使用责任链还是策略,使用策略模式的过滤很简单,组合并执行 doFilter 过滤方法,使用责任链的 append and next 会很酷。

如果使用责任链模式,接口会这样设计:

java 复制代码
public Interface IFilter {
    
    boolean doFilter(FilterContext context);
    
    IFilter next();
    
}

通过实现 doFilter 方法以及链式调用的 next 方法,类似于组装了一个链条,调用时,执行入口的过滤类,然后获取下一个过滤类,继续执行,在过滤结果为 true 的时候或者 next == null 时,责任链执行完成。

使用策略模式的接口会是这样:

java 复制代码
public Interface IFilter {
    
    boolean doFilter(FilterContext context);
    
}

调用时,拿到实现接口类的所有需要执行的过滤类集合,遍历集合并执行过滤。

在实际的业务中,阿布见到的绝大多数的过滤场景都是使用策略模式实现的,这种方式更为简单,可读性高。在偶尔的看到责任链实现的代码时,笔者也总是会考虑,使用策略模式是不是更加简单易懂,很多场景下,这两种模式只有形式上的差别,实际的执行逻辑是相同的。

工程里面的 facade

门面模式的概念实在是太宽泛了,在很多项目中,都会定义一个 facade 模块,这个模块就是服务对外暴露的接口模块,一个服务的门面就是它所提供的接口。

一个服务如此,服务里面的模块,模块里面的方法也是如此,当你考虑实现一个功能时,首先考虑的是怎么定义一个接口,怎么定义接口的入参、结果、方法名,这个过程就是搭建门面的过程。

联动的开关

有这样一个项目,需要你设计一个总的开关,三个子开关,子开关有 微信通知开关、短信通知开关、端内通知开关(当前应用的通知开关),其中打开任意子开关,总开关会联动打开;打开总开关,上次的子开关会联动打开;打开微信或者短信开关,端内开关会联动打开。

最直观的设计当然是,提供每个开关开启的接口。例如短信开关开启的接口,打开短信开关,联动打开总开关,再联动打开上次打开过的子开关,再打开端内开关。

java 复制代码
public void openMsgSwitch() {

    // 1.打开总开关
    this.openMainSwitch();
    
    // 2.打开上次打开过的开关
    this.openLastSwitch();
    
    // 3.打开端内开关
    this.openAppSwitch();

}

很复杂,而且这么多开关,我们需要提供非常多的接口,如果加上开关的关闭事件,就更加复杂了。

如果我们换一种思路,引入观察者模式的思想,每个子开关都订阅总开关的打开事件,端内开关订阅所有开关的打开事件,总开关订阅子开关的打开事件。

当短信开关开启,发生了变更,首先总开关收到订阅事件,总开关开启,然后各个子开关收到总开关的订阅事件,判断自己是否为上次打开的开关,如果是则打开,端内开关收到了其它开关开启的事件,直接开启。

这种思路是不是非常简单,可读性好,易于扩展,当后续新增一个开关时,只需要将该开关订阅相应的事件,并修改关联开关的订阅方法,就可以完成新开关的增加。

待续

阿布想到哪儿就记到哪儿,实际业务中设计模式的应用当然不止这些,也并非阿布写的这么简单,就比如刚说的开关,实际业务中是 2 个大开关,5 个子开关,交互的逻辑非常复杂,但内部处理的核心就是观察这种思想,把视角反转一下,程序的设计真的很有趣。

希望大家也能给阿布补充并指正,感谢!

相关推荐
淬渊阁4 小时前
Hello world program of Go
开发语言·后端·golang
Pandaconda4 小时前
【新人系列】Golang 入门(十五):类型断言
开发语言·后端·面试·golang·go·断言·类型
周Echo周4 小时前
16、堆基础知识点和priority_queue的模拟实现
java·linux·c语言·开发语言·c++·后端·算法
魔道不误砍柴功5 小时前
Spring Boot自动配置原理深度解析:从条件注解到spring.factories
spring boot·后端·spring
风象南5 小时前
基于Redis的3种分布式ID生成策略
redis·后端
魔道不误砍柴功6 小时前
Spring Boot 核心注解全解:@SpringBootApplication背后的三剑客
java·spring boot·后端
蓝天居士6 小时前
软考 系统架构设计师系列知识点 —— 设计模式之创建者模式
设计模式·系统架构·建造者模式
Asthenia04126 小时前
分布式唯一ID实现方案详解:数据库自增主键/uuid/雪花算法/号段模式
后端
Asthenia04126 小时前
内部类、外部类与静态内部类的区别详解
后端