解耦总的一句话来说,减少依赖,抽象业务和逻辑,让各个功能实现独立。
一.程序的耦合关系以及解耦的方式
1.耦合与解耦的概念
解耦:在软件工程中,降低耦合度即可以理解为解耦,模块间有依赖关系必然存在耦合,理论上的绝对零耦合是做不到的,但可以通过一些现有的方法将耦合度降至最低
2.程序中存在的耦合关系与解耦的方式
2.1 耦合简单的例子:
1 |
public class A{ |
以上代码就属于强耦合,B类中方法put要执行必须需要A类中的int属性i,缺了A,B中方法就不能执行
2.2 使用面向接口的编程方式实现上面例子的解耦:
1 |
public interface IBase{ |
这只是其中的一种情况
2.3 根据耦合程度分类
总结:
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
2.4 解耦
解耦的本质就是将类之间的直接关系转换成间接关系,不管是类向上转型还是接口回调都是在类之间加了一层,将原来的直接关系变成间接关系,使得两类对中间层是强耦合,两类之间变成弱耦合关系。
(a)采用现有设计模式实现解耦,如事件驱动模式、观察者模式、责任链模式等都可以达到解耦的目的;
(b)采用面向接口的方式编程,而不是用直接的类型引用,除非在最小内聚单元内部。但使用该方法解耦需要注意不要滥用接口。
(c)高内聚,往往会带来一定程度的低耦合度。高内聚决定了内部自行依赖,对外只提供必须的接口或消息对象,那么由此即可达成较低的耦合度。
( d )注解,以注解的方式,将方法,属性注入依赖,实现高扩展性。
解耦总的一句话来说,减少依赖,抽象业务和逻辑,让各个功能实现独立。
2.5 内聚与耦合
耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。
程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。
做业务不要先考虑“解耦“,而是要先满足业务流程。即你的程序的结构应该是一个个纵向的业务流,从controller到最后的数据存储。不同的业务流不会相互干扰。
等到你做了很多个业务流后,再去尝试辨识哪些地方有可能是能公用的,再去尝试去复用。公用的前提是
- 这块代码几乎很少改
- 这块代码有比较合理的业务抽象 —— 即从人类直觉上这给地方抽出来也是说得通的
- 就算这块代码要改。他的改动对其他依赖造成的影响总是符合常识的,而不是会引发一大堆问题
如果得不到这样的特性,即使代码看起来有相似之处,也不要去抽。错误的抽象才是代码维护的大敌。一旦做了,你根本控制不了你的修改的影响范围和影响程度。
题主对解耦的理解也有偏颇。从题目上看,似乎“逻辑不写一起“就解耦了,而写在一起就耦合。实际上并不是这样的。不要认为任何业务逻辑可以做任何细粒度的拆解。从业务角度,一个业务是一个“步骤的集合”。总会有第一步干什么,第二步干什么,等等。很多时候,这些步骤写一起才更容易看懂,更容易修改和维护。
更精细的区分,一个业务的逻辑大概可以分为两类:
- 业务直接相关的主逻辑。比如你要实现下单买东西,那么创建订单、扣款是主逻辑。这些逻辑必须要写到一起,而且往往用同一个事务包起来保证一致性。如果主逻辑非常复杂,就尝试用多个层级来拆解这些流程,但他们还是在一起。
- 业务辅助相关的逻辑。比如更新计数、更新ES的搜索数据、发一个业务统计log等。这些内容可以不要掺合到主逻辑中。可以用AOP,事件队列(单机的/分布式的)的形式来解耦。
但解耦是有前提的。事实上解耦会让系统结构更复杂了。比如一个业务接口调用完了,计数却没有增加,或者晚了很久才增加。要调查问题,就需要系统监控等做的相当到位。团队的相关模块负责人可以有办法来精确的管理跨系统的数据流,能快速定位错误原因。
同时,你的产品设计也可能需要跟着更改。比如计数可能因为最终一致性而暂时不准,会引起用户的困惑。这时候产品设计上可能需要一个临时的“假的,但是符合用户直觉“的计数,或者以各种方式隐藏计数等措施来对产品做调整。
解耦不是写业务代码的最终目标,用可以接受的性价比进行业务开发和代码维护才是。如果你的team里没有那么多人来各自负责独立的模块,也没有很好的INFRA支持,而是很少数几个人处理所有逻辑,那整体看下来还不如把代码都写一起。