本文字数:3161

预计阅读时间:20分钟

01

设计模式

设计模式的概念出自《Design Patterns - Elements of Reusable Object-Oriented Software》中文名是《设计模式 - 可复用的面向对象软件元素》,该书是在1994 年由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合力完成。书中提出的面向对象的设计模式七大基本准则也是我们在平时编码时要广泛遵守的,它们包括:

1、开闭原则(Open Closed Principle,OCP)

对外扩展开放,对内修改关闭。就是说新增需求时尽量通过扩展新类实现,而不是对原有代码修改增删。

2、单一职责原则(Single Responsibility Principle, SRP)

每个类、每个方法的职责(目的)都是单一的。不要让一个类或方法做太多纵向关联或横向并列的事,否则会增加维护负担。

3、里氏代换原则(Liskov Substitution Principle,LSP)

继承必须确保超类所拥有的性质在子类中仍然成立。就是只要父类出现的地方,都可以用子类替换。

4、依赖倒转原则(Dependency Inversion Principle,DIP) 

高层模块不应该依赖低层模块,二者都应该依赖其抽象。就是尽量使用接口来做标准和规范,降低耦合度。

5、接口隔离原则(Interface Segregation Principle,ISP)

接口的功能尽可能单一。一个接口只做一类行为或事物的标准。

6、合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)

类间关系尽量使用聚合、组合来实现,如果不可以的话再使用继承。目的还是为了降低类间耦合度,类间关系有六种,它们的耦合度大小关系是:泛化>实现>组合>聚合>关联>依赖,我们在设计架构时,尽量让类间关系靠近右边(就是采用耦合度更小的关系)。

7、最少知道原则(Least Knowledge Principle,LKP)/迪米特法则(Law of Demeter,LOD)

一个对象要对其他对象的成员尽可能少的知道。目的就是降低类间耦合度,提高代码的可复用性。

02

责任链模式

责任链模式属于行为型设计模式,英文名称Chain of Responsibility,其定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

责任链模式将对请求的处理过程划分为多个独立的处理节点,节点间互相不感知具体计算过程。处理节点存在前后顺序处理关系,也可以视具体的业务特点跳过某些节。

03

业务场景应用案例

1、业务背景

一个视频文件从用户电脑到发布到网络上通常要经历,上传、转码、审核、发布四个阶段,本文描述的业务场景适用于视频的审核与发布阶段。不同来源的视频审核策略不同,不同时期的视频审核策略不同,不同级别用户生成的视频审核策略不同等等。视频在审核阶段需要根据不同的审核策略进行处理。

2、不用责任链模式

在业务发展的初期,没有采用设计模式来指导编写代码。针对不同的审核策略处理视频的业务代码伪码如下:

if (videoInfo.getUploadFrom().equals("狐友") || videoInfo.getUploadFrom().equals("新闻")) {
}else{
}
if (videoInfo.getUploadFrom().equals("特邀作者上传") || videoInfo.getUploadFrom().equals("抓取来源")) {
} else{
}
if (videoInfo.getUploadFrom().equals("广告不适") || videoInfo.getUploadFrom().equals("严肃")) {
} else{
}
if ("命中MD5黑名单") {
} else{
}
if (videoInfo.getUploadFrom().equals("普通视频")) {
} else{
}
if (videoInfo.getUploadFrom().equals("免审标签") || videoInfo.getUploadFrom().equals("免审产品") || videoInfo.getUploadFrom().equals("免审用户")) {
} else{
}
if (videoInfo.getUploadFrom().equals("适用图片检测")) {
} else{
}
if (videoInfo.getUploadFrom().equals("适用音频检测")) {
} else{
}
if (videoInfo.getUploadFrom().equals("适用文本检测")) {
} else{
}
if (videoInfo.getUploadFrom().equals("适用全部类型AI检测")) {
} else{
}
if (videoInfo.getUploadFrom().equals("适用风险预测")) {
} else{
}
if (videoInfo.getUploadFrom().equals("适用基因检测")) {
} else{
}
if (now().between(time1, time2)&& isSensitiveAreaIp()) {
} else{
}
if (videoInfo.getUploadFrom().equals("适用产品策略检测")) {
} else{
}

代码使用了大量的if else语句,且如果随着业务的发展需要增加新的处理逻辑则又要对源码添加if else语句,违反了开闭原则,使得代码的可扩展性非常差。如果if else中的逻辑比较复杂,常常会由于增加新的逻辑而引入bug。这种业务代码特别适合使用责任链模型进行重构。

3、应用责任链模式

责任链模式的本质是将请求与处理解耦,允许将请求沿着处理器链进行发送。收到请求后,每个处理器均可对请求进行处理,或将其传递给链上的下个处理器。当新增处理逻辑时,不会对请求逻辑造成影响,两者互不干扰。示例图如下:

4、UML类图

通过对责任链模式的定义,需要定义 处理器接口或父类;具体的处理器类;请求接口或父类;具体的请求类;客户端类。但在实际的使用过程中,请求接口和请求类往往退化成一个request对象,在处理器链路里面传递,类图如下所示:

04

初级实现

我们根据uml类图,尝试一下如何应用责任链模式对原来的业务代码进行改造。首先定义一个Handler接口。

public interface Handler {void handle(Object videoInfo);void setNext(Handler handler);
}

然后构建三个具体的处理器实现类,实际工作中要对应原业务代码实现相应数量的处理器类,这里为了陈述方便,只实现三个处理器类。

1、处理器1:

public class Handler1 implements Handler {
private Handler next;@Overridepublic void handle(Object videoInfo) {System.out.println("handler1处理");if (next != null) {next.handle(videoInfo);}}@Overridepublic void setNext(Handler handler) {this.next = handler;}
}

2、处理器2:

public class Handler2 implements Handler {
private Handler next;@Overridepublic void handle(Object videoInfo) {System.out.println("handler2处理");if (next != null) {next.handle(videoInfo);}}@Overridepublic void setNext(Handler handler) {this.next = handler;}
}

3、处理器3:

public class Handler3 implements Handler {
private Handler next;@Overridepublic void handle(Object videoInfo) {System.out.println("handler3处理");if (next != null) {next.handle(videoInfo);}}@Overridepublic void setNext(Handler handler) {this.next = handler;}
}

4、最后是客户端代码,client:

public class Client {
public static void main(String[] args) {
Handler handler1 = new Handler1();
Handler handler2 = new Handler2();
Handler handler3 = new Handler3();handler1.setNext(handler2);handler2.setNext(handler3);handler1.handle("videoInfo");}
}
打印结果为:
handler1处理
handler2处理
handler3处理

上面示例中的实现实际上是责任链模式的一个变种,即每个处理器都做出了自己的处理,没有中途终止的情况。而责任链的编排工作全部由客户端client实现,且每个处理器除了要关注自己的处理器代码外,还需要持有下一个处理器的引用,以串联成链,并且还负责对下一个处理器的调用。违反了职责单一原则,并不利于系统的维护与扩展,耦合性还是比较高的。

05

进阶实现

上面示例的实现,并不利于系统的扩展,后续再增加处理器,仍然需要对client的代码进行改动,以把新增加的处理器编排入链,增加了拼错或拼少处理器的风险,实际上是clent的设计没有遵循单一职责的原则,承接了不属于其的职责导致,我们看看如何进一步的改造,来解决这个问题。可以尝试增加一个处理器链路管理模块负责承接处理器的编排职责,将处理器的编排职责从client代码中解放出来,uml类图如下:

首先仍然需要先定义一个Handler接口:

1、Handler接口如下:

public interface Handler {void handle(Object videoInfo);
}

这次接口只有一个handle方法了,去掉了setNext方法,因为责任链的编排交给链路管理类了,处理器的职责也变的单一了,只剩下了处理逻辑,十分简洁。

2、处理器1的实现:

public class Handler1 implements Handler {@Overridepublic void handle(Object videoInfo) {if (videoInfo.toString().contains("handler1")) {System.out.println("handler1处理");}}
}

这次加入了一个条件用于演示,仅当传入的request对象包含了handler1字符串时,才触发处理器1的处理,否则跳过处理器1。

3、处理器2的实现

public class Handler2 implements Handler {
private Handler next;@Overridepublic void handle(Object videoInfo) {System.out.println("handler2处理");}
}

4、处理器3的实现:

public class Handler3 implements Handler {
private Handler next;@Overridepublic void handle(Object videoInfo) {System.out.println("handler3处理");}
}

5、链路管理器HandlerChain的实现

public class HandlerChain {
private List<Handler> list = new ArrayList<>();public void add(Handler handler) {this.list.add(handler);}public void handle(Object videoInfo) {list.forEach(handler -> handler.handle(videoInfo));}
}

链路管理器负责处理器的编排,在本示例中,只是简单的按顺序调用每一个处理器进行处理,在实际工作中会增加处理器的优先级特性,也可以结合apollo等配置中心实现动态的处理器编排。

6、客户端client的代码

public class Client {
public static void main(String[] args) {
Handler handler1 = new Handler1();
Handler handler2 = new Handler2();
Handler handler3 = new Handler3();HandlerChain handlerChain = new HandlerChain();handlerChain.add(handler1);handlerChain.add(handler2);handlerChain.add(handler3);handlerChain.handle("videoInfo");}
}
运行结果为:
handler2处理
handler3处理

在这一个版本的实现中,客户端不再关心处理器的编排;处理器也不再持有下一个处理器的引用,只关注自身处理器业务代码的实现;把处理器的编排工作交给了链路管理器,随着业务增长需要新增处理器代码时,只需要修改链路管理器即可。但这似乎还是没有做到完全的遵守开闭原则,仍然需要修改链路管理器的源码,那么有没有一种实现可以完全做到遵守开闭原则呢? 

06

最终实现

想要完全的遵守开闭原则,即当新增处理器代码时,无需对已有的系统源码进行修改,我们需要结合框架来实现。而在实际的工作中是通过结合spring框架的控制反转与依赖注入特性实现的。首先将处理器的实现类定义为spring的bean,利用控制反转特性,将处理器交由spring框架管理。

1、示例代码为:

@Component
public class Handler1 implements Handler {@Overridepublic void handle(Object videoInfo) {if (videoInfo.toString().contains("handler1")) {System.out.println("handler1处理");}}
}

通过添加一个@Component注解,即可实现控制反转,将Handler1交由spring管理。其他处理器的实现类似。控制反转的特性也体现了依赖倒转原则,这里不再进一步讨论了。

然后利用依赖注入特性,将所有的控制器注入到链路管理器中。

2、示例代码为:

@Component
public class HandlerChain {
@Autowired
private List<Handler> list;public void handle(Object videoInfo) {list.forEach(handler -> handler.handle(videoInfo));}
}

可以看到链路管理器代码新增了@Component注解将其自身交由spring管理,然后在list属性上新增了@Autowired注解,spring会把其管理的所有实现了Handler接口的bean自动注入到该list对象中,这里遵循了里氏代换原则。当新增一个handler实现时,只要其实现了Handler接口,该处理器就会在spring启动时自动注入到链路管理器的list属性中,从而实现了完全遵守开闭原则,即当新增处理器逻辑时,无需对系统原有代码进行修改,只需要扩展新的处理器代码即可。

而客户端client的实现也会更加精简:

3、示例代码为:

@Component
public class Client {
@Autowired
private HandlerChain handlerChain;public void handle(Object videoInfo) {handlerChain.handle(videoInfo);}
}

同样的client也将自身交由spring管理,利用spring的控制反转与依赖注入特性实现了对链路管理器的调用。 

07

总结

通过使用责任链模式并遵循开闭原则、单一职责等设计原则对视频审核与发布业务进行了重构,当业务新增处理器时,无需对原有的系统源码进行修改,只需要关注新增的处理器本身即可,极大的增加了系统的灵活性与扩展性,也提高了系统的可维护性,减少了对复杂业务编排导致出现bug的几率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91652.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91652.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/91652.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

洛谷 P3372 【模板】线段树 1-普及+/提高

题目描述 如题&#xff0c;已知一个数列 {ai}\{a_i\}{ai​}&#xff0c;你需要进行下面两种操作&#xff1a; 将某区间每一个数加上 kkk。求出某区间每一个数的和。 输入格式 第一行包含两个整数 n,mn, mn,m&#xff0c;分别表示该数列数字的个数和操作的总个数。 第二行包含 n…

flink写paimon表的过程解析

背景 apache paimon是构建湖仓一体的重要组成部分&#xff0c;由于paimon的写入速度很快&#xff0c;通过flink进行数据写入是很自然的选择&#xff0c;本文就介绍下使用flink写入paimon的两阶段协议的大概逻辑 技术实现 flink通过两阶段协议写入paimon表&#xff0c;分成三个步…

迅为RK3568开发板OpeHarmony学习开发手册-点亮 HDMI 屏幕

OpenHarmony 源码中默认支持 HDMI 屏幕&#xff0c;但是默认的分辨率是采用 mipi 的分辨率&#xff0c;我们修改代码&#xff0c;关闭 MIPI 就可以正常显示了。在之前视频修改的基础上&#xff0c;修改/home/topeet/OH4.1/OpenHarmony-v4.1-Release/OpenHarmony/out/kernel/src…

北京理工大学医工交叉教学实践分享(1)|如何以实践破解数据挖掘教学痛点

如何有效提升医工交叉领域数据挖掘课程的教学效果&#xff1f;近日&#xff0c;北京理工大学医学技术学院辛怡副教授在和鲸组织的分享会上&#xff0c;系统介绍了其团队在《数据挖掘在生物医学中的应用》课程中的创新实践&#xff0c;为解决普遍教学痛点提供了可借鉴的“平台化…

Vue 3 入门教程 8 - 路由管理 Vue Router

一、Vue Router 简介Vue Router 是 Vue.js 官方的路由管理器&#xff0c;它与 Vue.js 核心深度集成&#xff0c;用于构建单页面应用&#xff08;SPA&#xff09;。单页面应用是指整个应用只有一个 HTML 页面&#xff0c;通过动态切换页面内容来模拟多页面跳转的效果&#xff0c…

django的数据库原生操作sql

from django.db import connection from django.db import transaction from django.db.utils import (IntegrityError,OperationalError,ProgrammingError,DataError ) from django.utils import timezoneclass Db(object):"""数据库操作工具类&#xff0c;封装…

FreeRTOS---基础知识2

1. FreeRTOS 调度机制概述FreeRTOS 是一个实时操作系统&#xff08;RTOS&#xff09;&#xff0c;其核心功能是通过 调度器&#xff08;Scheduler&#xff09; 管理多个任务的执行。调度机制决定了 何时切换任务 以及 如何选择下一个运行的任务&#xff0c;以满足实时性、优先级…

Docker安装(精简述版)

1. 安装&#xff1a;Docker 环境&#xff08;Docker desktop&#xff09; #Windows架构版本查看&#xff0c;Win R‌ 输入 ‌cmd‌ 打开命令提示符&#xff1b;输入命令‌&#xff1a; bash echo %PROCESSOR_ARCHITECTURE%#安装Docker desktop&#xff08;安装时勾选 WSL2&am…

Postman-win64-7.3.5-Setup.exe安装教程(附详细步骤+桌面快捷方式设置)​

Postman 是一款超常用的接口调试工具&#xff0c;程序员和测试人员用它来发送网络请求、测试API接口、调试数据交互​ 1. 双击安装包​ 安装包下载地址&#xff1a;https://pan.quark.cn/s/4b2960d60ae9&#xff0c;找到你下的 Postman-win64-7.3.5-Setup.exe 文件&#xff08…

149. Java Lambda 表达式 - Lambda 表达式的序列化

文章目录149. Java Lambda 表达式 - Lambda 表达式的序列化为什么要序列化 Lambda 表达式&#xff1f;Lambda 表达式的序列化规则示例代码&#xff1a;序列化 Lambda 表达式代码解析&#xff1a;Lambda 序列化的限制总结&#xff1a;149. Java Lambda 表达式 - Lambda 表达式的…

颐顿机电携手观远BI数据:以数据驱动决策,领跑先进制造智能化升级

颐顿机电签约观远数据&#xff0c;聚焦财务分析、销售管理等场景&#xff0c;以 BI 数据解决方案推进数据驱动决策&#xff0c;助力先进制造企业提效与竞争力升级。一、合作官宣&#xff1a;颐顿机电 观远数据&#xff0c;开启数据应用新征程浙江颐顿机电有限公司&#xff08;…

【PHP】几种免费的通过IP获取IP所在地理位置的接口(部分免费部分收费)

目录 一、获取客户端IP地址 二、获取IP所在地理位置接口 1、IP域名归属地查询 2、腾讯地图 - IP定位 3、聚合数据 - IP地址&#xff08;推荐&#xff09; 4、高德地图 - IP定位&#xff08;推荐&#xff09; 5、360分享计划 - IP查询 6、天聚ip地址查询 7、百度IP地址…

【Excel】制作双重饼图

一、效果话不多说&#xff0c;直接上数据和效果图&#xff01;&#xff08;示例软件&#xff1a;WPS Office&#xff09;类别现金刷卡小计苹果10.005.0015.00荔枝20.0015.0035.00西瓜30.0025.0055.00总计60.0045.00105.00二、步骤&#xff08;一&#xff09;制作底图插入饼图&a…

gcc-arm-none-eabi安装后,找不到libgcc.a的拉置

位置在&#xff1a;/usr/lib/gcc/arm-none-eabi/6.3.1/libgcc.a查找方法&#xff1a;arm-none-eabi-gcc --print-libgcc-file-name以前没找到&#xff0c;是因为进错目录&#xff1a;/usr/lib/arm-none-eabi/lib

上证50期权2400是什么意思?

本文主要介绍上证50期权2400是什么意思&#xff1f;“上证50期权2400”通常指上证50ETF期权的某个具体合约代码&#xff0c;其中“2400”是合约代码的一部分&#xff0c;需结合完整代码格式理解其含义。上证50期权2400是什么意思&#xff1f;一、上证50期权合约代码的组成上证5…

发那科机器人P点位置号码自动变更功能为禁用状态

通过改变变量的状态&#xff0c;发那科机器人可以实现&#xff0c;当在程序中进行记录、修改、插入、删除、复制/粘贴包含有P点位置号码的行时&#xff0c;P点位置号码会自动从小到大自动排列&#xff0c;可以实现自动排列&#xff0c;或者点击编辑变更编号也可以下图所示女变量…

什么叫湖仓一体

文章目录概念一、理解湖仓一体&#xff1a;先搞懂“数据湖”和“数据仓库”1. 数据仓库&#xff08;Data Warehouse&#xff09;2. 数据湖&#xff08;Data Lake&#xff09;3. 传统架构的痛点&#xff1a;“湖”与“仓”的割裂二、湖仓一体的核心特点&#xff1a;融合“湖”与…

网络安全突发事件应急预案方案

最近有要求需要出一个网络安全突发事件应急预案方案&#xff0c;本文仅就应急预案问题提出一点初步思考&#xff0c;意在抛砖引玉&#xff0c;盼各位读者不吝赐教&#xff0c;共同完善对这一领域的认识。一、总则 &#xff08;一&#xff09;目的 为有效应对规划建筑设计院企业…

【基于3D Gaussian Splatting的三维重建】保姆级教程 | 环境安装 | 制作-训练-测试自己数据集 | torch | colmap | ffmpeg | 全过程图文by.Akaxi

目录 一.【3DGS环境配置】 1.1 克隆3DGS仓库 1.2 安装Visual Studio 2022 1.2.1 下载Visual Studio 2022 1.2.2 更改环境变量 1.3 创建环境 1.3.1 创建python环境 1.3.2 离线安装torch包 1.3.3 安装依赖包 1.3.4安装子模块 &#xff08;1&#xff09;报错解决&…

C#泛型委托讲解

1. 泛型&#xff08;Generics&#xff09; 泛型允许编写类型安全且可重用的代码&#xff0c;避免装箱拆箱操作&#xff0c;提高性能。 泛型类 // 定义泛型类 public class GenericList<T> {private T[] items;private int count;public GenericList(int capacity){items …