欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习

1 复杂、繁杂、庞杂

在开发工作中我们经常会听到:这个业务很复杂,这个系统很复杂,这个逻辑很复杂,只要是处理遇到困难的场景,似乎都可以使用复杂这个词进行描述。

但是我认为困难之所以困难,原因还是有所不同的,不能用复杂这个词笼而统之,有加以区分的必要。大体上我认为可以分为复杂、繁杂、庞杂三个类型。

复杂和繁杂二者均包含分支多和逻辑多的含义,但是不同之处在于,复杂场景是可以理出头绪的,如果设计得当,是可以设计出很优雅的系统的。但是繁杂场景是难以理出头绪的,为了兼容只能打各种补丁,最终积重难返只能系统重构。

还有一种类型可以称之为庞杂,当数量达到一定规模时,复杂和繁杂都可以演化为庞杂。虽然同样是庞杂,但是也有复杂庞杂和繁杂庞杂的区别。本文只要讨论清楚复杂和庞杂,只要加上数量维度就是庞杂。

我们在开发中可以写复杂的代码,要尽量避免繁杂的代码,其中代码耦合就是一种典型的繁杂场景,模块间高度耦合的代码导致最终根本无法维护,本文我们讨论七种代码耦合类型。

2 代码耦合类型

七种代码耦合类型根据耦合程度由高到低排序分别是:内容耦合、公共耦合、外部耦合、控制耦合、标记耦合、数据耦合和非直接耦合。

2.1 内容耦合

一个模块可以直接访问另一个模块的内部数据被称为内容耦合,这是耦合性最强的类型,这也是我们需要尽量避免的。

假设模块A是订单模块,模块B是支付模块,如果支付模块可以直接访问订单数据表,那么至少会带来以下问题。

第一个问题是存在重复的数据访问层代码,支付和订单模块都要写订单数据访问代码。
第二个问题是如果订单业务变动,需要变更订单数据字段,如果支付模块没有跟着及时
变更,那么可能会造成业务错误。

第三个问题是如果订单业务变动,需要分库分表拆分数据,如果支付模块没有跟着及时变更,例如没有使用shardingKey进行查询或者旧库表停写,那么可能会造成支付模块严重错误。

第四个问题是业务入口没有收敛,访问入口到处散落,如果想要业务变更则需要多处修改,非常不利于维护。

2.2 公共耦合

多个模块都访问同一个公共数据环境被称为公共耦合,公共数据环境例如全局数据结构、共享通信区和内存公共覆盖区。

例如在项目中使用Apollo动态配置,配置项A内容是一段JSON,订单模块和支付模块均读取并解析这段数据结构进行业务处理。

public class ApolloConfig {@Value("${apollo.json.config}")private String jsonConfig;
}public class JsonConfig {public int type;public boolean switchOpen;
}public class OrderServiceImpl {public void createOrder() {String jsonConfig = apolloConfig.getJsonConfig();JsonConfig config = JSONUtils.toBean(jsonConfig, JsonConfig.class);if(config.getType() == TypeEnum.ORDER.getCode() && config.isSwitchOpen()) {createBizOrder();}}
}public class PayServiceImpl {public void createPayOrder() {String jsonConfig = apolloConfig.getJsonConfig();JsonConfig config = JSONUtils.toBean(jsonConfig, JsonConfig.class);if(config.getType() == TypeEnum.PAY.getCode() && config.isSwitchOpen()) {createBizPayOrder();}}
}

2.3 外部耦合

多个模块访问同一个全局简单变量(非全局数据结构)并且不是通过参数表传递此全局变量信息被称为外部耦合。

例如在项目中使用Apollo动态配置,配置项A内容是一个简单变量,订单模块和支付模块均读取这个简单变量进行业务处理。

public class ApolloConfig {@Value("${apollo.type.config}")private int typeConfig;
}public class OrderServiceImpl {public void createOrder() {if(apolloConfig.getTypeConfig() == TypeEnum.ORDER.getCode()) {createBizOrder();}}
}public class PayServiceImpl {public void createPayOrder() {if(apolloConfig.getTypeConfig() == TypeEnum.PAY.getCode()) {createBizPayOrder();}}
}

2.4 控制耦合

模块之间传递信息中包含用于控制模块内部的信息被称为控制耦合。控制耦合可能会导致模块之间控制逻辑相互交织,逻辑之间相互影响,非常不利于代码维护。

控制耦合代码实例如下,我们可以看到模块B代码逻辑重度依赖模块A类型,假设A类型发生了变化很可能就会影响B逻辑:

public class ModuleA {private int type;
}public class A {private B b = new B();public void methondA(int type) {ModuleA moduleA = new ModuleA(type);b.methondB(moduleA);}
}public class B {public void methondB(ModuleA moduleA) {if(moduleA.getType() == 1) {action1();} else if(moduleA.getType() == 2) {action2();}}
}

2.5 标记耦合

多个模块通过参数表传递数据结构信息被称为标记耦合,可以类比JAVA语言引用传递。

2.6 数据耦合

多个模块通过参数表传递简单数据信息被称为标记耦合,可以类比JAVA语言值传递。

2.7 非直接耦合

多个模块之间没有直接联系,通过主模块的控制和调用实现联系被称为非直接耦合,这也是一种理想的耦合方式。

我们重点谈一谈非直接耦合。复杂业务之所以复杂,一个重要原因是涉及角色或者类型较多,很难平铺直叙地进行设计。如果非要进行平铺设计,必然会出现大量if else代码块。

我们首先分析一个下单场景。当前有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);}
}

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

为了避免牵一发而动全身这种情况,设计模式中的开闭原则要求我们面向新增开放,面向修改关闭,我认为这是设计模式中最重要的一条原则。

需求变化通过扩展,而不是通过修改已有代码实现,这样就保证代码稳定性。扩展也不是随意扩展,因为事先定义了算法,扩展也是根据算法扩展,用抽象构建框架,用实现扩展细节。标准意义的二十三种设计模式说到底最终都是在遵循开闭原则。

如何改变平铺直叙的思考方式?我们需要增加分析维度。其中最常见的是增加横向和纵向两个维度,总体而言横向扩展的是思考广度,纵向扩展的是思考深度,对应到系统设计而言可以总结为:纵向做隔离,横向做编排。

这时我们可以为问题分析加上纵向和横向两个维度,选择使用分析矩阵方法,其中纵向表示策略,横向表示场景:

2.7.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.7.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);}
}

2.7.3 纵横思维

上述实例业务和代码并不复杂,其实复杂业务场景也不过是简单场景的叠加、组合和交织,无外乎也是通过纵向做隔离、横向做编排寻求答案。

纵向维度抽象出能力池这个概念,能力池中包含许多能力,不同的能力按照不同业务维度聚合,例如优惠能力池,物流能力池,退款能力池。我们可以看到两种程度的隔离性,能力池之间相互隔离,能力之间也相互隔离。

横向维度将能力从能力池选出来,按照业务需求串联在一起,形成不同业务流程。因为能力可以任意组合,所以体现了很强的灵活性。除此之外,不同能力既可以串行执行,如果不同能力之间没有依赖关系,也可以如同流程Y一样并行执行,提升执行效率。

3 文章总结

第一本文区分了复杂、繁杂、庞杂这一组概念,复杂和繁杂虽然都比较难处理,但是复杂是可以理出头绪的,而繁杂最终会积重难返。我们应该尽量避免繁杂的代码。复杂和繁杂加上数量维度就成为庞杂。

第二本文介绍了七种代码耦合类型,根据耦合程度由高到低排序分别是:内容耦合、公共耦合、外部耦合、控制耦合、标记耦合、数据耦合和非直接耦合。我们应该尽量写耦合度低的代码。

第三本文由一个复杂订单场景实例出发,重点介绍了非直接耦合类型,可以看到即使是复杂场景,通过合理的设计也可以优雅实现,希望本文对大家有所帮助。

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习

复杂、繁杂、庞杂:图解七种代码耦合类型相关推荐

  1. 编程语言静态/动态类型,强/弱定义分类以及七种程序错误类型

    Dikstra说过:如果debugging是移除bug的过程,那打代码一定是把bug们放进去的过程. 编程语言如果按照类型检查分类可以分为两种static typing静态类型 和 dynamic t ...

  2. C++七种序列容器类型

    七种序列容器 1,vector-向量 2,deque-双端队列 3,list-双向链表 4,forward_list-单链表(C++11) 5,queue-队列(适配器) ,6,priority_qu ...

  3. Gridview数据控件的七种字段类型

    9.8  数据控件的七种字段类型(Fields Type)的应用 GridView共支持七种字段类型,字段原本应该叫"Column"比较恰当,但ASP.NET 2.0却采用另一个名 ...

  4. Mysql的七种表类型

    转载自http://database.51cto.com/art/201010/230057.htm 学习Mysql数据库,Mysql表类型都有哪些是一定需要知道的,下面就为您介绍七种Mysql表类型 ...

  5. 耦合(六种)与 内聚(七种)—《软件工程与计算》笔记

    耦合(六种)与 内聚(七种) 1. 耦合(六种) 耦合描述的是 两个模块 之间关系的复杂程度.耦合性越低越好,耦合度越高,模块划分越差,越不利于软件变更和复用.根据耦合性的高低依次分为以下六种耦合: ...

  6. 多图详解:七种具体方法增强代码可扩展性

    欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析.实际应用.架构思维.职场分享.产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习 1 六大原则 在设计 ...

  7. 常用七种排序之冒泡排序(排序图解+分析Java

    hello呀!各位,这里是Sunlightʊə. 目前大三,主要在学习Java语言.可以一起交流呀! 相关文章: 常用七种排序之选择排序(排序图解+分析Java 常用七种排序之希尔排序(排序图解+分析 ...

  8. 耦合关系从强到弱顺序_图解7种耦合关系

    之前组内同学问我耦合的关系,我没给对方讲清楚,今天借这个机会来深入讲讲模块之间的耦合关系这个事情. 本文将用图文详细讲解七种耦合的不同之处. 高内聚与低耦合 高内聚与低耦合是每个软件开发者追求的目标, ...

  9. 耦合和内聚-图解7种耦合关系

    之前组内同学问我耦合的关系,我没给对方讲清楚,今天借这个机会来深入讲讲模块之间的耦合关系这个事情. 本文将用图文详细讲解七种耦合的不同之处. 高内聚与低耦合 高内聚与低耦合是每个软件开发者追求的目标, ...

最新文章

  1. python 图表_python导出excel charts图表
  2. python使用matplotlib可视化折线图、在可视化图像中同时绘制多条折线图
  3. 给图像特征提取开个“ViP”是什么效果?字节牛津提出视觉解析器,全面超越HaloNet!...
  4. linux selinux 安全上下文 修改
  5. 图片裁切,上传,自动匹配颜色。
  6. 网页切图的CSS和布局经验与要点
  7. linux下oracle11g的安装-图文安装
  8. Ubuntu 安装 Libmodbus
  9. 普通进销存管理系统设计
  10. 阿里 机器翻译 api
  11. Math常用的数学运算(包括取整、取绝对值、保留几位小数等)
  12. php的作品简介怎么写,作品简介(参赛作品简介怎么写)
  13. 为什么python叫爬虫_python为什么叫爬虫
  14. MOBA移动游戏性能分析报告:渲染、UI和逻辑代码是性能头号杀手!
  15. 考研:2023寒假复习及2022复试问题
  16. 绿卡日记:2020-11-20
  17. 调研容易上岸的在职研究生
  18. 神了!用Python预测世界杯决赛,发现准确率还挺高!
  19. Python中 -m pip install -U --force-reinstall pip 个人经验及解决办法
  20. ROS小车应用:控制机器人做圆周运动

热门文章

  1. java错误光标的闪烁怎么设置_【CSS3】自定义设置可编辑元素闪烁光标的颜色
  2. 关于计算机游戏的英语读法,[计算机游戏用英语怎么说]电脑游戏用英语怎么说...
  3. 摄像头各参数的意义_如何选择摄像头,需要考虑那些参数
  4. 如何用Python给图片添加文字/图片水印的方法,特别简单好用,filestools和Pil模块
  5. ISCC 2019 逆向rev02
  6. PlantCV中文文档
  7. Visual Studio 2017 智能提示英文怎么切换成中文?
  8. XJTU大学计算机编程作业题 第9周
  9. Win11分磁盘怎么分?Win11系统怎么分磁盘?
  10. office365服务器没有响应,office 365 使用过程中频繁出现无响应