关于接口、依赖、耦合,我的一些想法
开发人员经常提到2个词,“依赖”和“耦合”。最近的工作是一个系统的重构,在这方面想得比较多,在此总结一下
一、对模块的理解
模块可以在逻辑和物理2个层面上进行划分
对于比较小的工程,可能会把所有的模块都放在一个工程里。这样的话,不同的模块仅仅是在逻辑上有区别,在物理上还是一致的,因此也就不存在由于依赖而无法编译的问题
对于比较大的工程,除了在逻辑上划分之外,不同的模块往往还会放在不同的工程中,这时理顺模块之间的依赖关系,就尤为重要。因为如果存在依赖缺失,或者循环依赖等问题的话,整个项目就无法编译
二、对依赖和耦合的理解
首先我认为,在开发层面,设计是为了解决职责分配、依赖和耦合的问题,本文仅讨论依赖和耦合
只要2个模块需要协同工作来完成一个功能,这2个模块就一定存在依赖和耦合。如果2个模块完全独立,没有任何依赖,实际上这2个模块就没有关系,没必要放到一起说
有些场景,2个模块在代码层面没有任何依赖,而是通过数据库、文件、JMS等其它方式来集成,那么可以认为这2个模块不存在“编译依赖”,但是依赖依然存在。假如模块A不再遵守约定的JMS消息格式,那么模块B实际上也无法继续正常使用
因此,模块之间只有紧耦合和松耦合的区别,不存在完全不耦合的模块,除非它们毫无关系。我们设计的目的,也就是使2个模块之间的依赖尽可能地减少,达到松耦合
某种意义上,我觉得耦合和依赖是同一个概念
画2个方框,分别代表模块A和模块B,有一种比较直观的方法,来判断这2个模块的耦合程度和依赖情况:如果模块A里大量依赖了模块B中的代码,那么可以简单地认为,这2个模块是紧耦合的;如果模块B仅通过若干个清晰的接口暴露给模块A,那么就认为,这2个模块是松耦合的
当然这种判断方法是否正确,只是我的个人意见
比如说,存在一个医院就诊系统,模块A是患者,模块B是医院行政部门。那么就存在2种设计。一种是患者直接依赖模块B中的挂号、诊室、药房、收银。另一种是采用Facade设计模式,患者仅仅依赖模块B中的“接待”。
采用前者,模块A就至少依赖了模块B的4个类,或者说,模块A了解模块B的实现细节。采用后者的话,模块A仅仅依赖模块B的1个类,或者说,模块A对模块B的实现细节是一无所知的。这2种设计放在一起比较,很明显是后者优于前者,而且后者的依赖关系也更加清晰
三、对接口的理解
首先,我认为不能狭义地把接口理解为interface关键字。如前文所说,有些模块或者子系统之间,是通过JMS来集成的。不管双方内部是如何实现,最终通过JMS Message来传递消息。这种情况下,就认为JMS消息是模块间的接口
关于接口,有很多说法,比如“不要针对实现编程,要针对接口编程”、“没有接口,就没有设计”等等。对于这些说法,每个人都有不同的理解,我个人对于接口有以下看法:
1、有利于多模块协同开发
接口可以认为是模块之间的契约。在开发之初,首先把接口确定下来。然后各模块就可以各自开发,只要保证遵守接口的契约即可。
比如说,2个模块通过JMS集成,那么只要把消息格式确定下来,2个模块的开发就相互不影响了
再比如说,模块A依赖模块B的InterfaceB接口,那么只要InterfaceB确定下来,模块A就可以自行进行内部的开发,不用去关心模块B的实现情况。同时也解决了编译依赖的问题,因为模块A除了InterfaceB之外,不依赖模块B的任何代码
2、有利于清晰模块间的依赖
前面说过,协同工作的模块之间肯定存在依赖关系。但是到底怎么依赖,就很有讲究,不同的依赖方式,肯定会造成松耦合和紧耦合的区别
以经典的创建对象为例,从直接new实例,到工厂,到现在的依赖注入,耦合程度不断降低
以下代码都在模块A中,假设模块A用到了模块B的Car的实例
@Autowiredprivate Car car;