七种方法增强代码可扩展性(多图详解)

article/2025/8/4 11:02:39

1 六大原则

在设计模式中有六大设计原则:

单一职责原则:一个类只做一件事

里式替换原则:子类可以扩展父类

依赖倒置原则:面向接口编程

接口隔离原则:高内聚低耦合

迪米特法则:最少知道原则

开闭原则:关闭修改,开放新增

我认为在这六个原则中开闭原则最为重要,开闭原则是可扩展性的重要基石。

第一个原因是需求变化时应该通过新增而不是修改已有代码实现,这样保证了代码稳定性,避免牵一发而动全身。

第二个原因是可以事先定义代码框架,扩展也是根据框架扩展,体现了用抽象构建框架,用实现扩展细节,保证了稳定性也保证了灵活性。

第三个原因是其它五个原则虽然侧重点各有不同,但是都可以包含于开闭原则。

第四个原因是标准二十三种设计模式最终都是在遵循开闭原则。

既然开闭原则如此重要,我们应该怎么在系统设计时遵循开闭原则呢?

2 数据库维度

2.1 设计类型字段

数据表设计时新增两列:biz_type、biz_sub_type,这两个列即使目前用不上,但是后续扩展可以派上大用场,可以先设置一个默认值。

biz_type可以指业务类型,业务渠道、租户等等,归根结底是用来隔离大业务类型,假设后续业务新增了一个大业务类型,那么可以通过这个字段隔离。

biz_sub_type可以在大业务类型中细分小业务,使得业务更加细化,灵活性更好。

2.2 设计扩展字段

数据表设计时新增三列:extend1、extend2、extend3,可以先设置为空。扩展字段存储JSON类型信息,存放一些扩展信息、附加信息、松散信息或者是之前未预估到的信息,保证了灵活性。

之所以设置三个扩展字段是为了增加隔离性,例如extend1存放订单扩展,extend2存放商品扩展,extend3存放营销扩展。

2.3 设计业务二进制字段

2.3.1 需求背景

数据表设计时新增业务二进制,这个字段可以很大程度上扩展业务表意能力。假设在系统中用户一共有三种角色:普通用户、管理员、超级管理员,现在需要设计一张用户角色表记录这类信息。我们不难设计出如下方案:

idnamesuperadminnormal
101用户一100
102用户二010
103用户三001
104用户四111

观察上表不难得出,用户一具有超级管理员角色,用户二具有管理员角色,用户三具有普通用户角色,用户四同时具有三种角色。如果此时新增加一种角色呢?那么新增一个字段即可。

2.3.2 发现问题

按照上述一个字段表示一种角色进行表设计功能上是没有问题的,优点是容易理解结构清晰,但是我们想一想有没有什么问题?笔者遇到过如下问题:在复杂业务环境一份数据可能会使用在不同的场景,例如上述数据存储在MySQL数据库,这一份数据还会被用在如下场景:

检索数据需要同步一份到ES

业务方使用此表通过Flink计算业务指标

业务方订阅此表Binlog消息进行业务处理

如果表结构发生变化,数据源之间就要重新进行对接,业务方也要进行代码修改,这样开发成本比较非常高。有没有办法避免此类问题?

2.3.3 解决方案

我们可以使用位图法,这样同一个字段可以表示多个业务含义。首先设计如下数据表,userFlag字段暂时不填。

idnameuser_flag
101用户一暂时不填
102用户二暂时不填
103用户三暂时不填
104用户四暂时不填

设计位图每一个bit表示一种角色:

使用位图法表示如下数据表:

idnamesuperadminnormal
101用户一100
102用户二010
103用户三001
104用户四111

用户一位图如下其十进制数值等于4:

用户二位图如下其十进制数值等于2:

 用户三位图如下其十进制数值等于1:

 用户四位图如下其十进制数值等于7:

 现在我们可以填写数据表第三列:

idnameuser_flag
101用户一4
102用户二2
103用户三1
104用户四7

2.3.4 代码实例

定义枚举时不要直接定义为1、2、4这类数字,而应该采用位移方式进行定义,这样使用者可以明白设计者的意图。

/*** 用户角色枚举** @author **/
public enum UserRoleEnum {// 1 -> 00000001NORMAL(1, "普通用户"),// 2 -> 00000010MANAGER(1 << 1, "管理员"),// 4 -> 00000100SUPER(1 << 2, "超级管理员");private int code;private String description;private UserRoleEnum(Integer code, String description) {this.code = code;this.description = description;}public String getDescription() {return description;}public int getCode() {return this.code;}
}
复制代码

假设用户已经具有普通用户角色,我们需要为其增加管理员角色,这就是新增角色,与之对应还有删除角色和查询角色,这些操作需要用到为位运算,详见代码注释。

/*** 用户角色枚举** @author **/
public enum UserRoleEnum {// 1 -> 00000001NORMAL(1, "普通用户"),// 2 -> 00000010MANAGER(1 << 1, "管理员"),// 4 -> 00000100SUPER(1 << 2, "超级管理员");// 新增角色 -> 位或操作// oldRole -> 00000001 -> 普通用户// addRole -> 00000010 -> 新增管理员// newRole -> 00000011 -> 普通用户和管理员public static Integer addRole(Integer oldRole, Integer addRole) {return oldRole | addRole;}// 删除角色 -> 位异或操作// oldRole -> 00000011 -> 普通用户和管理员// delRole -> 00000010 -> 删除管理员// newRole -> 00000001 -> 普通用户public static Integer removeRole(Integer oldRole, Integer delRole) {return oldRole ^ delRole;}// 是否有某种角色 -> 位与操作// allRole -> 00000011 -> 普通用户和管理员// qryRole -> 00000001 -> 是否有管理员角色// resRole -> 00000001 -> 有普通用户角色public static boolean hasRole(Integer allRole, Integer qryRole) {return qryRole == (role & qryRole);}private int code;private String description;private UserRoleEnum(Integer code, String description) {this.code = code;this.description = description;}public String getDescription() {return description;}public int getCode() {return this.code;}public static void main(String[] args) {System.out.println(addRole(1, 2));System.out.println(removeRole(3, 1));System.out.println(hasRole(3, 1));}
}
复制代码

假设在运营后台查询界面中,需要查询具有普通用户角色的用户数据,可以使用如下SQL语句:

select * from user_role where (user_flag & 1) = user_flag;
select * from user_role where (user_flag & b'0001') = user_flag;
复制代码

我们也可以使用MyBatis语句:

<select id="selectByUserRole" resultMap="BaseResultMap" parameterType="java.util.Map">select * from user_role where user_flag & #{userFlag} = #{userFlag}
</select><select id="selectByUserIdAndRole" resultMap="BaseResultMap" parameterType="java.util.Map">select * from user_role where id = #{userId} and user_flag & #{userFlag} = #{userFlag}
</select>
复制代码

3 接口维度

3.1 设计类型入参

接口输入参数一定要设计类型入参,可以与数据库biz_type、biz_sub_type相对应,也可以自定义类型最终翻译为与数据库类型相对应,

如果一开始不用区分类型可以设置为默认值,但是其核心思想是一定要可以通过类型对输入参数进行区分。

public class OrderDTO {private Integer bizType;private Integer bizSubType;private Long amount;private String goodsId;
}public Response<String> createOrder(OrderDTO order);
复制代码

3.2 设计松散入参

接口输入参数可以设计Map类型松散参数,松散参数缺点是表意能力弱,优点是灵活性强,可以以较低成本新增参数。

public class OrderDTO {private Integer bizType;private Integer bizSubType;private Long amount;private String goodsId;private Map<String, String> params;
}public Response<String> createOrder(OrderDTO order);
复制代码

3.3 设计接口版本号

对外接口需要设计版本号,对于同一个接口,允许外部业务逐渐切到新版本,新老版本接口会共存一段时间,通过版本号区分。

/order/1.0/createOrder
/order/1.1/createOrder
复制代码

3.4 纵横做设计

我们分析一个下单场景。当前有ABC三种订单类型:A订单价格9折,物流最大重量不能超过9公斤,不支持退款。B订单价格8折,物流最大重量不能超过8公斤,支持退款。C订单价格7折,物流最大重量不能超过7公斤,支持退款。按照需求字面含义平铺直叙地写代码也并不难:

public class OrderServiceImpl implements OrderService {@Resourceprivate OrderMapper orderMapper;@Overridepublic void createOrder(OrderBO orderBO) {if (null == orderBO) {throw new RuntimeException("参数异常");}if (OrderTypeEnum.isNotValid(orderBO.getType())) {throw new RuntimeException("参数异常");}// A类型订单if (OrderTypeEnum.A_TYPE.getCode().equals(orderBO.getType())) {orderBO.setPrice(orderBO.getPrice() * 0.9);if (orderBO.getWeight() > 9) {throw new RuntimeException("超过物流最大重量");}orderBO.setRefundSupport(Boolean.FALSE);}// B类型订单else if (OrderTypeEnum.B_TYPE.getCode().equals(orderBO.getType())) {orderBO.setPrice(orderBO.getPrice() * 0.8);if (orderBO.getWeight() > 8) {throw new RuntimeException("超过物流最大重量");}orderBO.setRefundSupport(Boolean.TRUE);}// C类型订单else if (OrderTypeEnum.C_TYPE.getCode().equals(orderBO.getType())) {orderBO.setPrice(orderBO.getPrice() * 0.7);if (orderBO.getWeight() > 7) {throw new RuntimeException("超过物流最大重量");}orderBO.setRefundSupport(Boolean.TRUE);}// 保存数据OrderDO orderDO = new OrderDO();BeanUtils.copyProperties(orderBO, orderDO);orderMapper.insert(orderDO);}
}
复制代码

上述代码从功能上完全可以实现业务需求,但是程序员不仅要满足功能,还需要思考代码的可维护性。如果新增一种订单类型,或者新增一个订单属性处理逻辑,那么我们就要在上述逻辑中新增代码,如果处理不慎就会影响原有逻辑。

如何改变平铺直叙的思考方式?这就要为问题分析加上纵向和横向两个维度,我选择使用分析矩阵方法,其中纵向表示策略,横向表示场景:

 

(1) 纵向做隔离

纵向维度表示策略,不同策略在逻辑上和业务上应该是隔离的,本实例包括优惠策略、物流策略和退款策略,策略作为抽象,不同订单类型去扩展这个抽象,策略模式非常适合这种场景。本文详细分析优惠策略,物流策略和退款策略同理。

// 优惠策略
public interface DiscountStrategy {public void discount(OrderBO orderBO);
}// A类型优惠策略
@Component
public class TypeADiscountStrategy implements DiscountStrategy {@Overridepublic void discount(OrderBO orderBO) {orderBO.setPrice(orderBO.getPrice() * 0.9);}
}// B类型优惠策略
@Component
public class TypeBDiscountStrategy implements DiscountStrategy {@Overridepublic void discount(OrderBO orderBO) {orderBO.setPrice(orderBO.getPrice() * 0.8);}
}// C类型优惠策略
@Component
public class TypeCDiscountStrategy implements DiscountStrategy {@Overridepublic void discount(OrderBO orderBO) {orderBO.setPrice(orderBO.getPrice() * 0.7);}
}// 优惠策略工厂
@Component
public class DiscountStrategyFactory implements InitializingBean {private Map<String, DiscountStrategy> strategyMap = new HashMap<>();@Resourceprivate TypeADiscountStrategy typeADiscountStrategy;@Resourceprivate TypeBDiscountStrategy typeBDiscountStrategy;@Resourceprivate TypeCDiscountStrategy typeCDiscountStrategy;public DiscountStrategy getStrategy(String type) {return strategyMap.get(type);}@Overridepublic void afterPropertiesSet() throws Exception {strategyMap.put(OrderTypeEnum.A_TYPE.getCode(), typeADiscountStrategy);strategyMap.put(OrderTypeEnum.B_TYPE.getCode(), typeBDiscountStrategy);strategyMap.put(OrderTypeEnum.C_TYPE.getCode(), typeCDiscountStrategy);}
}// 优惠策略执行
@Component
public class DiscountStrategyExecutor {private DiscountStrategyFactory discountStrategyFactory;public void discount(OrderBO orderBO) {DiscountStrategy discountStrategy = discountStrategyFactory.getStrategy(orderBO.getType());if (null == discountStrategy) {throw new RuntimeException("无优惠策略");}discountStrategy.discount(orderBO);}
}
复制代码

(2) 横向做编排

横向维度表示场景,一种订单类型在广义上可以认为是一种业务场景,在场景中将独立的策略进行串联,模板方法设计模式适用于这种场景。

模板方法模式一般使用抽象类定义算法骨架,同时定义一些抽象方法,这些抽象方法延迟到子类实现,这样子类不仅遵守了算法骨架约定,也实现了自己的算法。既保证了规约也兼顾灵活性,这就是用抽象构建框架,用实现扩展细节。

// 创建订单服务
public interface CreateOrderService {public void createOrder(OrderBO orderBO);
}// 抽象创建订单流程
public abstract class AbstractCreateOrderFlow {@Resourceprivate OrderMapper orderMapper;public void createOrder(OrderBO orderBO) {// 参数校验if (null == orderBO) {throw new RuntimeException("参数异常");}if (OrderTypeEnum.isNotValid(orderBO.getType())) {throw new RuntimeException("参数异常");}// 计算优惠discount(orderBO);// 计算重量weighing(orderBO);// 退款支持supportRefund(orderBO);// 保存数据OrderDO orderDO = new OrderDO();BeanUtils.copyProperties(orderBO, orderDO);orderMapper.insert(orderDO);}public abstract void discount(OrderBO orderBO);public abstract void weighing(OrderBO orderBO);public abstract void supportRefund(OrderBO orderBO);
}// 实现创建订单流程
@Service
public class CreateOrderFlow extends AbstractCreateOrderFlow {@Resourceprivate DiscountStrategyExecutor discountStrategyExecutor;@Resourceprivate ExpressStrategyExecutor expressStrategyExecutor;@Resourceprivate RefundStrategyExecutor refundStrategyExecutor;@Overridepublic void discount(OrderBO orderBO) {discountStrategyExecutor.discount(orderBO);}@Overridepublic void weighing(OrderBO orderBO) {expressStrategyExecutor.weighing(orderBO);}@Overridepublic void supportRefund(OrderBO orderBO) {refundStrategyExecutor.supportRefund(orderBO);}
}
复制代码

4 文章总结

本文介绍了设计类型字段、设计扩展字段、设计业务二进制字段、设计类型入参、设计松散入参、设计接口版本号、纵横做设计七种增加系统可扩展性具体方案,希望本文对大家有所帮助。


http://chatgpt.dhexx.cn/article/CnWFIE2C.shtml

相关文章

系统复杂度之【可扩展性】

紧接着我们来聊聊可扩展性。 可扩展性是指&#xff0c;软件系统具备面对未来需求变化而进行扩展的能力。系统可根据新的需求做出少量或者不需要修改&#xff0c;无需对整个系统进行重构或重建。 由于软件系统变化多端&#xff0c;新的需求不断提出&#xff0c;因此可扩展性非常…

php文字链接下划线怎么取消,html超链接怎么去掉下划线

html超链接怎么去掉下划线&#xff1f; 可以用css的text-decoration:none来取消连接的下划线 这是一个链接 扩展资料&#xff1a; 超级文本标记语言是标准通用标记语言下的一个应用&#xff0c;也是一种规范&#xff0c;一种标准&#xff0c;它通过标记符号来标记要显示的网页中…

html去除超链接标记,html超链接去掉下划线的方法

html超链接去掉下划线的方法 发布时间&#xff1a;2020-04-03 15:16:00 来源&#xff1a;亿速云 阅读&#xff1a;59 作者&#xff1a;小新 今天小编给大家分享的是html超链接去掉下划线的方法&#xff0c;很多人都不太了解&#xff0c;今天小编为了让大家更加了解html超链接去…

<el-tabs>改变样式(去掉下划线及其他样式)

elemen-ui官方文档中给的标签页是蓝色的&#xff0c;长这样 我想要的样子&#xff1a; 这里重点是去掉原来的下划线以及鼠标悬停时的背景颜色&#xff0c;话不多说直接上代码&#xff1a; <div class"leftTabs"><el-tabs v-model"activeName" c…

html怎么消除链接字体,CSS去掉超链接下划线

div css之去掉A超链接下划线样式 一、去掉字体下划线装饰CSS属性样式代码 text-decoration:none 解释&#xff1a; CSS下划线样式单词&#xff1a;text-decoration 不显示下划线(去掉字体下划线样式)&#xff1a;none 二、div css网页重构前初始化去掉超链接下划线 由于有的浏览…

html更改超链接下划线颜色,超链接去掉下划线_WORD中不取消超链接,只改超链接的颜色和去掉下划线...

ppt超链接去掉下划线 1、首先打开PPT。2、“插入”“文本框”“横排文本框”,在空白处写字,调整字的大小等。3、插入”“超链接”,会出现“插入超链接”对话框,在这里选择需要链接到的位置,点击确定。4、这时出现的超链接字体就会有下划线。 5、我们在这里选中超链接字体右…

html超链接下划线改虚线_html超链接去掉下划线 html去除取消超链接下划线

大多时候我们知道&#xff1a;text-decoration:underline &#xfffd;?strong>显示下划&#xfffd;?/strong>&#xfffd;?/p> html中去除去&#xfffd;?a href"//www.css5.com.cn/html/989.shtml">A标签超链接下划线代码&#xfffd;?br /> …

php中超链接怎么去下划线的,html如何去掉超链接下划线?html超链接去掉下划线的方法介绍...

前端网页的开发中总是不可避免会使用到超链接&#xff0c;但是使用超链接时下面都会有一条下划线&#xff0c;这在网页中有时会显得很突兀&#xff0c;不合适也不美观&#xff0c;所以我们就需要将超链接下的这条下划线给去掉&#xff0c;接下来的这篇文章就来给大家介绍html下…

html5中链接去除下划线,html超链接去掉下划线 html去除取消超链接下划线

html a超链接标签&#xff0c;默认有的涉猎器表现有下划线&#xff0c;有的不有下划线&#xff0c;大多锚文本超链接A标签内字体是有下划线的&#xff0c;怎么去除超链接下划线&#xff1f;html 超链接去除下划线怎么样做&#xff1f; 去掉去除超链接锚文本的下划线需要CSS花样…

html如何取消表格的横线,怎么去掉下划线样式?

怎么去掉超链接下划线?下面本篇文章给大家介绍一下在html网页和word文档中去掉下划线样式的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 html代码中去掉超链接的下划线 前端网页的开发中总是不可避免会使用到超链接,但是使用超链接时下面都会有…

word里边页眉下面的下划线怎么去掉

原创作品&#xff0c;出自 “深蓝的blog” 博客&#xff0c;欢迎转载&#xff0c;转载时请务必注明以下出处&#xff0c;否则追究版权法律责任。 深蓝的blog&#xff1a;http://blog.csdn.net/huangyanlong/article/details/43762599 今日在使用word编辑统计表时&#xff0c;添…

为什么电流镜的复制电路是二极管结构?

左图是电流镜的概念图&#xff0c;复制电路偏置在IREF&#xff0c;为右边的管子提供一个偏压VGS。右图是最典型的电流镜结构&#xff0c;今天的问题是为什么复制电路结构用二极管连接结构&#xff0c;不用CS&#xff0c;CD或者CG中的一种呢&#xff1f; 首先我们得明确复制电路…

电流镜电路公式推导

最近复习了硬件笔试题。碰到了这个题&#xff0c;遂写一下自己的推导过程。 电流镜原理图&#xff08;假设三个管子的是参数理想一致的情况&#xff09; 基于 KCL 公式 -->>> Ie1 Ib2 Ib3 基于 BJT放大关系 -->>> Ie1 Ib1 *(1β)基于 KCL 公式 -->…

有源电流镜-下-第十学时

第10学时&#xff0c;电流镜电路&#xff0c;已发布&#xff0c;详见网易云课堂系列精品课程&#xff1a;《CMOS模拟集成电路设计》&#xff0c;由陈博士主讲&#xff0c;链接地址&#xff1a; https://study.163.com/course/introduction.htm?share2&shareId40000000067…

电流镜电流--第10学时

第10学时&#xff0c;电流镜电路&#xff0c;​已发布&#xff0c;详见网易云课堂系列精品课程&#xff1a;《CMOS模拟集成电路设计》&#xff0c;由陈博士主讲&#xff0c;​链接地址&#xff1a; https://study.163.com/course/introduction.htm?share2&shareId40000000…

电流镜电路

控制左边的电流 镜像到右边电流

cascode电流镜

下面的镜像管决定电流。

用1:3电流镜恒定GM的输入输出轨到轨运放设计(自用一)

工艺及指标 本项目采用CSMC .25工艺 它的工作范围为2.7V至5.5V&#xff08;共模电压&#xff09; GBW增益带宽积为6.5MHz SR压摆率为5V/μs 5V时静态电流为670μA 提供轨到轨的输入输出范围。 原理图及原理分析 来源&#xff1a;参考sansen《模拟集成电路设计精粹》P211…

电子技术——电流镜负载的差分放大器

电子技术——电流镜负载的差分放大器 目前我们学习的差分放大器都是使用的是差分输出的方式&#xff0c;即在两个漏极之间获取电压。差分输出主要有以下优势&#xff1a; 降低了共模信号的增益&#xff0c;提高了共模抑制比。降低了输入偏移电压。提升了差分输入的增益。 由于…