复杂、繁杂、庞杂:图解七种代码耦合类型
1 复杂、繁杂、庞杂
在开发工作中我们经常会听到:这个业务很复杂,这个系统很复杂,这个逻辑很复杂,只要是处理遇到困难的场景,似乎都可以使用复杂这个词进行描述。
但是我认为困难之所以困难,原因还是有所不同的,不能用复杂这个词笼而统之,有加以区分的必要。大体上我认为可以分为复杂、繁杂、庞杂三个类型。
复杂和繁杂二者均包含分支多和逻辑多的含义,但是不同之处在于,复杂场景是可以理出头绪的,如果设计得当,是可以设计出很优雅的系统的。但是繁杂场景是难以理出头绪的,为了兼容只能打各种补丁,最终积重难返只能系统重构。
还有一种类型可以称之为庞杂,当数量达到一定规模时,复杂和繁杂都可以演化为庞杂。虽然同样是庞杂,但是也有复杂庞杂和繁杂庞杂的区别。本文只要讨论清楚复杂和庞杂,只要加上数量维度就是庞杂。
我们在开发中可以写复杂的代码,要尽量避免繁杂的代码,其中代码耦合就是一种典型的繁杂场景,模块间高度耦合的代码导致最终根本无法维护,本文我们讨论七种代码耦合类型。
2 代码耦合类型
七种代码耦合类型根据耦合程度由高到低排序分别是:内容耦合、公共耦合、外部耦合、控制耦合、标记耦合、数据耦合和非直接耦合。
2.1 内容耦合
一个模块可以直接访问另一个模块的内部数据被称为内容耦合,这是耦合性最强的类型,这也是我们需要尽量避免的。
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 { @Resource private OrderMapper orderMapper; @Override public 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 { @Override public void discount(OrderBO orderBO) { orderBO.setPrice(orderBO.getPrice() * 0.9); } } // B类型优惠策略 @Component public class TypeBDiscountStrategy implements DiscountStrategy { @Override public void discount(OrderBO orderBO) { orderBO.setPrice(orderBO.getPrice() * 0.8); } } // C类型优惠策略 @Component public class TypeCDiscountStrategy implements DiscountStrategy { @Override public void discount(OrderBO orderBO) { orderBO.setPrice(orderBO.getPrice() * 0.7); } } // 优惠策略工厂 @Component public class DiscountStrategyFactory implements InitializingBean { private Map<String, DiscountStrategy> strategyMap = new HashMap<>(); @Resource private TypeADiscountStrategy typeADiscountStrategy; @Resource private TypeBDiscountStrategy typeBDiscountStrategy; @Resource private TypeCDiscountStrategy typeCDiscountStrategy; public DiscountStrategy getStrategy(String type) { return strategyMap.get(type); } @Override public 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 { @Resource private 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 { @Resource private DiscountStrategyExecutor discountStrategyExecutor; @Resource private ExpressStrategyExecutor expressStrategyExecutor; @Resource private RefundStrategyExecutor refundStrategyExecutor; @Override public void discount(OrderBO orderBO) { discountStrategyExecutor.discount(orderBO); } @Override public void weighing(OrderBO orderBO) { expressStrategyExecutor.weighing(orderBO); } @Override public void supportRefund(OrderBO orderBO) { refundStrategyExecutor.supportRefund(orderBO); } }
2.7.3 纵横思维
上述实例业务和代码并不复杂,其实复杂业务场景也不过是简单场景的叠加、组合和交织,无外乎也是通过纵向做隔离、横向做编排寻求答案。
纵向维度抽象出能力池这个概念,能力池中包含许多能力,不同的能力按照不同业务维度聚合,例如优惠能力池,物流能力池,退款能力池。我们可以看到两种程度的隔离性,能力池之间相互隔离,能力之间也相互隔离。
横向维度将能力从能力池选出来,按照业务需求串联在一起,形成不同业务流程。因为能力可以任意组合,所以体现了很强的灵活性。除此之外,不同能力既可以串行执行,如果不同能力之间没有依赖关系,也可以如同流程Y一样并行执行,提升执行效率。
3 文章总结
第一本文区分了复杂、繁杂、庞杂这一组概念,复杂和繁杂虽然都比较难处理,但是复杂是可以理出头绪的,而繁杂最终会积重难返。我们应该尽量避免繁杂的代码。复杂和繁杂加上数量维度就成为庞杂。
第二本文介绍了七种代码耦合类型,根据耦合程度由高到低排序分别是:内容耦合、公共耦合、外部耦合、控制耦合、标记耦合、数据耦合和非直接耦合。我们应该尽量写耦合度低的代码。
第三本文由一个复杂订单场景实例出发,重点介绍了非直接耦合类型,可以看到即使是复杂场景,通过合理的设计也可以优雅实现,希望本文对大家有所帮助。
来源:https://mp.weixin.qq.com/s/PsSi6lMbXMeN9BLSXZ7vKA