耦合必然性

简介: 最近学到一个词“耦合创伤应激障碍”,讲的是程序员对耦合条件反射式恐惧,对于这个新词,我再重新理解一篇对于一名程序员,从入行开始,就听到前辈们对“高内聚低耦合”的谆谆教诲,所以对于低耦合的意识深入骨髓。知行合一,看看是怎么践行的,打开任何一个项目工程,可以看到,每一个service都有一个interface和impl,代码看起来整整齐齐,所有变化点都考虑到了,但其实没有降低问题复杂度,只是自己看着舒服

最近学到一个词“耦合创伤应激障碍”,讲的是程序员对耦合条件反射式恐惧,对于这个新词,我再重新理解一篇

对于一名程序员,从入行开始,就听到前辈们对“高内聚低耦合”的谆谆教诲,所以对于低耦合的意识深入骨髓。知行合一,看看是怎么践行的,打开任何一个项目工程,可以看到,每一个service都有一个interface和impl,代码看起来整整齐齐,所有变化点都考虑到了,但其实没有降低问题复杂度,只是自己看着舒服

《SOLID总结》[1]中提到过面向接口编程中接口到底是什么含义,并不是所有实现类都得需要一个接口,才是面向接口编程

而现在实践中对实现依赖心理恐惧,成了一种行业通病,见不得对实现的依赖,这是典型的耦合创伤应激障碍,像“猴子实验”

五只猴子被关进笼子里,笼子一角挂着一串香蕉,如果有猴子试图摘取香蕉,就会被开水泼到。猴子们吃了几次苦头之后,就再也不想摘香蕉了。此时用一只新猴子替换老猴子,新猴子看到有香蕉刚想去摘,就被老猴子们拉住一顿暴打。新猴子挨了几次打之后,也不再去摘香蕉了。此时再换进一只新猴子,它也看到香蕉想去摘,也被老猴子们一顿暴打,下手最狠的恰恰是那一只没被开水烫到过的。最后老猴子们都被换干净了,仍然没有猴子去碰那串香蕉,因为它们知道——碰香蕉意味着被打,而为什么会被打,没有猴子知道。

当参与一个新项目,不再创建interface时,肯定会变成那只被打的“猴子”

然而现实并不是这样的,真的加个interface就减少耦合了吗?耦合少得了吗?比如,需要使用支付宝或微信支付,那么这就是业务需求,与支付宝和微信就必然会耦合,才能达到业务要求。不管怎么组织代码结构,是明显直接调用,还是隐晦地抛出支付事件,终将需要调用支付宝微信的支付接口;再比如现在很多应用需要推送消息,短信、邮件亦或微信,那么与支付类似,不管如何,必将调用第三方接口才能实现这些功能,这也就是耦合的必然性

代码大致是这样的:

先来一个接口:

package com.zhuxingsheng.infrastructure.port
public interface AlipayService {
    public PayResult pay(AliInfo aliInfo,decimal payAmount);
}

再来对接口的具体实现,调用支付宝SDK

package com.zhuxingsheng.infrastructure.apapter
public class AlipayServiceImpl implements AlipayService {
    public PayResult pay(AliInfo aliInfo,decimal payAmount) {
        AlipaySdk.pay();
        return result;
    }
}

微信支付代码结构类似,对于这些代码味道是不是相当熟悉,有service必有interface和impl,但看看接口的意义在哪儿?

机智的你,肯定发现这样不对,我们需要的应该是在线支付能力,而支付宝支付或微信支付只是具体的实现而已,也就是与支付宝和微信耦合其实不是必然的,必然的是在线支付能力

这儿其实有两种演变过程:

第一种:先实现了支付宝支付功能,当再实现微信支付时,此时发现要抽象出在线支付接口,策略模式

第二种:从业务需求用户故事:作为用户需要完成订单线上支付,完成订单的全流程

第一种从技术入手,而第二种从最原始的业务入手,这两种演变虽然第二种方是大道,可技术人却喜欢第一种,这也就回到篇首所说,代码看起来整整齐齐,却没有简化问题,甚至只因为技术风格的强统一,带来了业务语义的更加隐晦

技术其实是解决业务的工具,需要从业务本源着手,挖掘业务背后隐藏的业务能力,构建出对应的技术模型,达到模型即代码的统一,也就知道了必然性耦合,怎么降低耦合

所以再看上面的接口,package com.zhuxingsheng.infrastructure.port,是在基础设施层,只是技术上单纯的一个接口,在《DDD》[2]专栏中,提到过的接口在领域层,实现在基础设施层,因为maven模块循环依赖或者DIP的需要,所以必须要把接口放在领域层,从业务角度分析,线上支付能力是构建模型的必要能力,是领域模型必不可少的部分,需要在线支付能力,也是业务必然性耦合的体现,应该在领域层

可从代码形式上看,你是不是觉得不管是对业务的深刻理解,还是单纯技术抽象,不都是一个接口吗?无非是叫接口还是叫业务能力而已吗?

再看一个接口


package com.zhuxingsheng.infrastructure.port
public interface UserService {
    public User getUser(long userId);
}

从命名就知道,获取用户信息,不管是业务系统自身处理用户信息,还是从用户中心式外部服务获取用户信息,这是整个系统的基本能力,而不会再需要去抽象一个深奥的业务能力,当业务需求故事阐述用户下订单业务行为时,业务方已然抽象了整个用户体系,只有研究用户上下文子域问题时,才去深入用户领域模型,但从当前业务上下文看,业务系统与这些基础服务是深度绑定,并在系统架构初始已经确定了整体架构

因此在业务系统中去定义一个userservice接口,是没啥意义的,除非系统大动,否则是不会变动的

延伸一下,此时领域层如何依赖基础设施层呢?怎么DIP呢?有没有丝毫感受到分层有问题呢。对此下回分解

总结一下,我们需要正确看待耦合必然性,不是从技术实现的角度去硬生抽象,而需要从业务角度,挖掘出业务真正耦合的能力,坦然接受这样的耦合,清晰化表达业务语义

References

[1] 《SOLID总结》: http://www.zhuxingsheng.com/blog/solid-summary.html

[2] 《DDD》: http://www.zhuxingsheng.com/tags/DDD/


目录
相关文章
|
监控 程序员 Linux
你管这破玩意叫 IO 多路复用?
为了讲多路复用,当然还是要跟风,采用鞭尸的思路,先讲讲传统的网络 IO 的弊端,用拉踩的方式捧起多路复用 IO 的优势。 为了方便理解,以下所有代码都是伪代码,知道其表达的意思即可。
807 0
你管这破玩意叫 IO 多路复用?
|
8月前
|
SQL 安全 关系型数据库
MySQL UDF提权
通过这些内容的详细介绍和实际案例分析,希望能帮助您深入理解MySQL UDF提权的机制、实现步骤及防范措施,提高系统的安全性和防护能力。
580 11
|
9月前
|
JSON 前端开发 JavaScript
Java属性为什么不能是is开头的boolean
在Java实体类中,阿里规约要求boolean属性不应以is开头。文章通过实际案例分析了isUpdate字段在JSON序列化过程中变为update的问题,并提供了自定义get方法或使用@JSONField注解两种解决方案,建议遵循规约避免此类问题。
253 0
Java属性为什么不能是is开头的boolean
|
12月前
|
Java Spring
springboot静态资源目录访问,及自定义静态资源路径,index页面的访问
本文介绍了Spring Boot中静态资源的访问位置、如何进行静态资源访问测试、自定义静态资源路径和静态资源请求映射,以及如何处理自定义静态资源映射对index页面访问的影响。提供了两种解决方案:取消自定义静态资源映射或编写Controller来截获index.html的请求并重定向。
springboot静态资源目录访问,及自定义静态资源路径,index页面的访问
|
存储 Java 程序员
Java面试题:请解释Java中的永久代(PermGen)和元空间(Metaspace)的区别
Java面试题:请解释Java中的永久代(PermGen)和元空间(Metaspace)的区别
499 11
|
存储 关系型数据库 MySQL
阿里巴巴MYSQL 开发规范
阿里巴巴MYSQL 开发规范
1388 0
|
JavaScript 前端开发 Java
Vue CLI脚手架安装、搭建、配置 和 CLI项目分析
Vue CLI脚手架搭建和分析 详解。
360 0
|
关系型数据库 MySQL 测试技术
【专栏】PostgreSQL数据库向MySQL迁移的过程、挑战及策略
【4月更文挑战第29天】本文探讨了PostgreSQL数据库向MySQL迁移的过程、挑战及策略。迁移步骤包括评估规划、数据导出与转换、创建MySQL数据库、数据导入。挑战包括数据类型不匹配、函数和语法差异、数据完整性和性能问题。应对策略涉及数据类型映射、代码调整、数据校验和性能优化。迁移后需进行数据验证、性能测试和业务验证,确保顺利过渡。在数字化时代,掌握数据库迁移技能对技术人员至关重要。
818 5
|
JavaScript 前端开发
vue项目引入阿里巴巴矢量图标库图标及其使用教程
vue项目引入阿里巴巴矢量图标库图标及其使用教程
2982 0
|
SQL 关系型数据库 MySQL
Springboot 调用mysql的.sql文件,执行mysql语句
Springboot 调用mysql的.sql文件,执行mysql语句
978 0
Springboot 调用mysql的.sql文件,执行mysql语句