IT评测·应用市场-qidao123.com

标题: 七种代码耦合范例的图解:你的属于哪一种? [打印本页]

作者: 梦应逍遥    时间: 2024-10-6 08:45
标题: 七种代码耦合范例的图解:你的属于哪一种?
1 复杂、繁杂、繁芜

在开发工作中我们经常会听到:这个业务很复杂,这个体系很复杂,这个逻辑很复杂,只要是处理碰到困难的场景,好像都可以利用复杂这个词进行形貌。
但是我认为困难之所以困难,原因还是有所差别的,不能用复杂这个词笼而统之,有加以区分的必要。大要上我认为可以分为复杂、繁杂、繁芜三个范例。
复杂和繁杂二者均包含分支多和逻辑多的寄义,但是差别之处在于,复杂场景是可以理出头绪的,如果设计得当,是可以设计出很优雅的体系的。但是繁杂场景是难以理出头绪的,为了兼容只能打各种补丁,终极积重难返只能体系重构。
另有一种范例可以称之为繁芜,当数量达到一定规模时,复杂和繁杂都可以演化为繁芜。虽然同样是繁芜,但是也有复杂繁芜和繁杂繁芜的区别。本文只要讨论清楚复杂和繁芜,只要加上数量维度就是繁芜。
我们在开发中可以写复杂的代码,要只管避免繁杂的代码,其中代码耦合就是一种典型的繁杂场景,模块间高度耦合的代码导致终极根本无法维护,本文我们讨论七种代码耦合范例。

2 代码耦合范例

七种代码耦合范例根据耦合水平由高到低排序分别是:内容耦合、公共耦合、外部耦合、控制耦合、标志耦合、数据耦合和非直接耦合。


 
 

2.1 内容耦合

一个模块可以直接访问另一个模块的内部数据被称为内容耦合,这是耦合性最强的范例,这也是我们必要只管避免的。


 

假设模块A是订单模块,模块B是付出模块,如果付出模块可以直接访问订单数据表,那么至少会带来以下问题。
第一个问题是存在重复的数据访问层代码,付出和订单模块都要写订单数据访问代码。 第二个问题是如果订单业务变动,必要变动订单数据字段,如果付出模块没有跟着及时 变动,那么可能会造成业务错误。
第三个问题是如果订单业务变动,必要分库分表拆分数据,如果付出模块没有跟着及时变动,比方没有利用shardingKey进行查询大概旧库表停写,那么可能会造成付出模块严峻错误。
第四个问题是业务入口没有收敛,访问入口随处散落,如果想要业务变动则必要多处修改,非常倒霉于维护。

2.2 公共耦合

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


 

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

  1. public class ApolloConfig {
  2.     @Value("${apollo.json.config}")
  3.     private String jsonConfig;
  4. }
  5. public class JsonConfig {
  6.     public int type;
  7.     public boolean switchOpen;
  8. }
  9. public class OrderServiceImpl {
  10.     public void createOrder() {
  11.         String jsonConfig = apolloConfig.getJsonConfig();
  12.         JsonConfig config = JSONUtils.toBean(jsonConfig, JsonConfig.class);
  13.         if(config.getType() == TypeEnum.ORDER.getCode() && config.isSwitchOpen()) {
  14.             createBizOrder();
  15.         }
  16.     }
  17. }
  18. public class PayServiceImpl {
  19.     public void createPayOrder() {
  20.         String jsonConfig = apolloConfig.getJsonConfig();
  21.         JsonConfig config = JSONUtils.toBean(jsonConfig, JsonConfig.class);
  22.         if(config.getType() == TypeEnum.PAY.getCode() && config.isSwitchOpen()) {
  23.             createBizPayOrder();
  24.         }
  25.     }
  26. }
复制代码

2.3 外部耦合

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


 

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

  1. public class ApolloConfig {
  2.     @Value("${apollo.type.config}")
  3.     private int typeConfig;
  4. }
  5. public class OrderServiceImpl {
  6.     public void createOrder() {
  7.         if(apolloConfig.getTypeConfig() == TypeEnum.ORDER.getCode()) {
  8.             createBizOrder();
  9.         }
  10.     }
  11. }
  12. public class PayServiceImpl {
  13.     public void createPayOrder() {
  14.         if(apolloConfig.getTypeConfig() == TypeEnum.PAY.getCode()) {
  15.             createBizPayOrder();
  16.         }
  17.     }
  18. }
复制代码

2.4 控制耦合

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


 

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

  1. public class ModuleA {
  2.     private int type;
  3. }
  4. public class A {
  5.     private B b = new B();
  6.     public void methondA(int type) {
  7.         ModuleA moduleA = new ModuleA(type);
  8.         b.methondB(moduleA);
  9.     }
  10. }
  11. public class B {
  12.     public void methondB(ModuleA moduleA) {
  13.         if(moduleA.getType() == 1) {
  14.             action1();
  15.         } else if(moduleA.getType() == 2) {
  16.             action2();
  17.         }
  18.     }
  19. }
复制代码

2.5 标志耦合

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


 

2.6 数据耦合

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


 

2.7 非直接耦合

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


 

我们重点谈一谈非直接耦合。复杂业务之所以复杂,一个重要原因是涉及角色大概范例较多,很难平铺直叙地进行设计。如果非要进行平铺设计,必然会出现大量if else代码块。
我们首先分析一个下单场景。当前有ABC三种订单范例:A订单代价9折,物流最大重量不能超过9公斤,不支持退款。B订单代价8折,物流最大重量不能超过8公斤,支持退款。C订单代价7折,物流最大重量不能超过7公斤,支持退款。按照需求字面寄义平铺直叙地写代码也并不难:

  1. public class OrderServiceImpl implements OrderService {
  2.     @Resource
  3.     private OrderMapper orderMapper;
  4.     @Override
  5.     public void createOrder(OrderBO orderBO) {
  6.         if (null == orderBO) {
  7.             throw new RuntimeException("参数异常");
  8.         }
  9.         if (OrderTypeEnum.isNotValid(orderBO.getType())) {
  10.             throw new RuntimeException("参数异常");
  11.         }
  12.         // A类型订单
  13.         if (OrderTypeEnum.A_TYPE.getCode().equals(orderBO.getType())) {
  14.             orderBO.setPrice(orderBO.getPrice() * 0.9);
  15.             if (orderBO.getWeight() > 9) {
  16.                 throw new RuntimeException("超过物流最大重量");
  17.             }
  18.             orderBO.setRefundSupport(Boolean.FALSE);
  19.         }
  20.         // B类型订单
  21.         else if (OrderTypeEnum.B_TYPE.getCode().equals(orderBO.getType())) {
  22.             orderBO.setPrice(orderBO.getPrice() * 0.8);
  23.             if (orderBO.getWeight() > 8) {
  24.                 throw new RuntimeException("超过物流最大重量");
  25.             }
  26.             orderBO.setRefundSupport(Boolean.TRUE);
  27.         }
  28.         // C类型订单
  29.         else if (OrderTypeEnum.C_TYPE.getCode().equals(orderBO.getType())) {
  30.             orderBO.setPrice(orderBO.getPrice() * 0.7);
  31.             if (orderBO.getWeight() > 7) {
  32.                 throw new RuntimeException("超过物流最大重量");
  33.             }
  34.             orderBO.setRefundSupport(Boolean.TRUE);
  35.         }
  36.         // 保存数据
  37.         OrderDO orderDO = new OrderDO();
  38.         BeanUtils.copyProperties(orderBO, orderDO);
  39.         orderMapper.insert(orderDO);
  40.     }
  41. }
复制代码

上述代码从功能上完全可以实现业务需求,但是步伐员不仅要满足功能,还必要思索代码的可维护性。如果新增一种订单范例,大概新增一个订单属性处理逻辑,那么我们就要在上述逻辑中新增代码,如果处理不慎就会影响原有逻辑。
为了避免牵一发而动全身这种环境,设计模式中的开闭原则要求我们面向新增开放,面向修改关闭,我认为这是设计模式中最重要的一条原则。
需求变化通过扩展,而不是通过修改已有代码实现,如许就保证代码稳定性。扩展也不是随意扩展,由于事先界说了算法,扩展也是根据算法扩展,用抽象构建框架,用实现扩展细节。标准意义的二十三种设计模式说到底终极都是在遵循开闭原则。
如何改变平铺直叙的思索方式?我们必要增加分析维度。其中最常见的是增加横向和纵向两个维度,总体而言横向扩展的是思索广度,纵向扩展的是思索深度,对应到体系设计而言可以总结为:纵向做隔离,横向做编排。
这时我们可以为问题分析加上纵向和横向两个维度,选择利用分析矩阵方法,其中纵向表示计谋,横向表示场景:


 

2.7.1 纵向做隔离

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

  1. // 优惠策略
  2. public interface DiscountStrategy {
  3.     public void discount(OrderBO orderBO);
  4. }
  5. // A类型优惠策略
  6. @Component
  7. public class TypeADiscountStrategy implements DiscountStrategy {
  8.     @Override
  9.     public void discount(OrderBO orderBO) {
  10.         orderBO.setPrice(orderBO.getPrice() * 0.9);
  11.     }
  12. }
  13. // B类型优惠策略
  14. @Component
  15. public class TypeBDiscountStrategy implements DiscountStrategy {
  16.     @Override
  17.     public void discount(OrderBO orderBO) {
  18.         orderBO.setPrice(orderBO.getPrice() * 0.8);
  19.     }
  20. }
  21. // C类型优惠策略
  22. @Component
  23. public class TypeCDiscountStrategy implements DiscountStrategy {
  24.     @Override
  25.     public void discount(OrderBO orderBO) {
  26.         orderBO.setPrice(orderBO.getPrice() * 0.7);
  27.     }
  28. }
  29. // 优惠策略工厂
  30. @Component
  31. public class DiscountStrategyFactory implements InitializingBean {
  32.     private Map<String, DiscountStrategy> strategyMap = new HashMap<>();
  33.     @Resource
  34.     private TypeADiscountStrategy typeADiscountStrategy;
  35.     @Resource
  36.     private TypeBDiscountStrategy typeBDiscountStrategy;
  37.     @Resource
  38.     private TypeCDiscountStrategy typeCDiscountStrategy;
  39.     public DiscountStrategy getStrategy(String type) {
  40.         return strategyMap.get(type);
  41.     }
  42.     @Override
  43.     public void afterPropertiesSet() throws Exception {
  44.         strategyMap.put(OrderTypeEnum.A_TYPE.getCode(), typeADiscountStrategy);
  45.         strategyMap.put(OrderTypeEnum.B_TYPE.getCode(), typeBDiscountStrategy);
  46.         strategyMap.put(OrderTypeEnum.C_TYPE.getCode(), typeCDiscountStrategy);
  47.     }
  48. }
  49. // 优惠策略执行
  50. @Component
  51. public class DiscountStrategyExecutor {
  52.     private DiscountStrategyFactory discountStrategyFactory;
  53.     public void discount(OrderBO orderBO) {
  54.         DiscountStrategy discountStrategy = discountStrategyFactory.getStrategy(orderBO.getType());
  55.         if (null == discountStrategy) {
  56.             throw new RuntimeException("无优惠策略");
  57.         }
  58.         discountStrategy.discount(orderBO);
  59.     }
  60. }
复制代码

2.7.2 横向做编排

横向维度表示场景,一种订单范例在广义上可以认为是一种业务场景,在场景中将独立的计谋进行串联,模板方法设计模式实用于这种场景。
模板方法模式一样平常利用抽象类界说算法骨架,同时界说一些抽象方法,这些抽象方法延迟到子类实现,如许子类不仅遵守了算法骨架约定,也实现了本身的算法。既保证了规约也兼顾机动性,这就是用抽象构建框架,用实现扩展细节。

  1. // 创建订单服务
  2. public interface CreateOrderService {
  3.     public void createOrder(OrderBO orderBO);
  4. }
  5. // 抽象创建订单流程
  6. public abstract class AbstractCreateOrderFlow {
  7.     @Resource
  8.     private OrderMapper orderMapper;
  9.     public void createOrder(OrderBO orderBO) {
  10.         // 参数校验
  11.         if (null == orderBO) {
  12.             throw new RuntimeException("参数异常");
  13.         }
  14.         if (OrderTypeEnum.isNotValid(orderBO.getType())) {
  15.             throw new RuntimeException("参数异常");
  16.         }
  17.         // 计算优惠
  18.         discount(orderBO);
  19.         // 计算重量
  20.         weighing(orderBO);
  21.         // 退款支持
  22.         supportRefund(orderBO);
  23.         // 保存数据
  24.         OrderDO orderDO = new OrderDO();
  25.         BeanUtils.copyProperties(orderBO, orderDO);
  26.         orderMapper.insert(orderDO);
  27.     }
  28.     public abstract void discount(OrderBO orderBO);
  29.     public abstract void weighing(OrderBO orderBO);
  30.     public abstract void supportRefund(OrderBO orderBO);
  31. }
  32. // 实现创建订单流程
  33. @Service
  34. public class CreateOrderFlow extends AbstractCreateOrderFlow {
  35.     @Resource
  36.     private DiscountStrategyExecutor discountStrategyExecutor;
  37.     @Resource
  38.     private ExpressStrategyExecutor expressStrategyExecutor;
  39.     @Resource
  40.     private RefundStrategyExecutor refundStrategyExecutor;
  41.     @Override
  42.     public void discount(OrderBO orderBO) {
  43.         discountStrategyExecutor.discount(orderBO);
  44.     }
  45.     @Override
  46.     public void weighing(OrderBO orderBO) {
  47.         expressStrategyExecutor.weighing(orderBO);
  48.     }
  49.     @Override
  50.     public void supportRefund(OrderBO orderBO) {
  51.         refundStrategyExecutor.supportRefund(orderBO);
  52.     }
  53. }
复制代码

2.7.3 纵横思维

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


 

纵向维度抽象出能力池这个概念,能力池中包含很多能力,差别的能力按照差别业务维度聚合,比方优惠能力池,物流能力池,退款能力池。我们可以看到两种水平的隔离性,能力池之间相互隔离,能力之间也相互隔离。
横向维度将能力从能力池选出来,按照业务需求串联在一起,形成差别业务流程。由于能力可以恣意组合,所以体现了很强的机动性。除此之外,差别能力既可以串行执行,如果差别能力之间没有依赖关系,也可以犹如流程Y一样并行执行,提拔执行效率。

3 文章总结

第一本文区分了复杂、繁杂、繁芜这一组概念,复杂和繁杂虽然都比较难处理,但是复杂是可以理出头绪的,而繁杂终极会积重难返。我们应该只管避免繁杂的代码。复杂和繁杂加上数量维度就成为繁芜。
第二本文介绍了七种代码耦合范例,根据耦合水平由高到低排序分别是:内容耦合、公共耦合、外部耦合、控制耦合、标志耦合、数据耦合和非直接耦合。我们应该只管写耦合度低的代码。
第三本文由一个复杂订单场景实例出发,重点介绍了非直接耦合范例,可以看到纵然是复杂场景,通过合理的设计也可以优雅实现,希望本文对大家有所资助。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4