开发人员经常提到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的实例
Benz car = new Benz();
这种方式显然是最差的,因为依赖的是具体的实现类,如果后面换了一种车型,不但客户端(模块A)需要修改,而且依赖关系完全是不稳定的。现在依赖Benz类,后面可能就依赖BMW类
Car car = new Benz();
这种方式稍微好了一点,有了接口的定义,至少调用方法是稳定的了,但是还是没有解决依赖不稳定的问题
Car car = CarFactory.getInstance("Benz");
这种方式就比前一种更进了一步,不但调用方法稳定,而且依赖也是稳定的,现在不管模块B有多少种Car的实现类,模块A都仅仅依赖于Car接口和CarFactory类。但是依然是由客户端来负责创建,当模块B变更了实现类,客户端的代码依然需要修改
@Autowired
private Car car;
这就是现在的依赖注入,除了具备上述工厂模式的优点之外,连创建实例的职责,也转移到了依赖注入框架,模块A和模块B的依赖达到了最低(当然如上文所说,依赖仍然存在)
依赖接口,而不是依赖实现类,这种依赖比较稳定,而且范围也比较明确
3、模块内部比较稳定的类之间,不需要接口
有一种做法,就是很死板地规定一个实现类对应一个接口,哪怕在模块内部,也通过接口来调用,这种方式我倒是持保留意见,暂时没看到有什么好处
4、在三层架构中,依赖接口编程
这个和上一条有点类似,大部分项目里,在DAO层,都会有一组DAO接口对应一组DAO实现,但是实际上,替换DAO的实现类倒是几乎不会发生
但是因为这层接口,可以作为2层之间的界限,所以我个人觉得还是有意义的,可以保留
分享到:
相关推荐
在听取了小李的想法之后,架构师提出了一些改进意见。他建议采用依赖注入(Dependency Injection, DI)的方式来进行设计。这种方式可以让组件之间的依赖关系更加清晰,易于管理和扩展。 - **依赖注入**是一种软件...
- **Type1接口注入**:通过定义接口的方法来注入依赖项。这种方式比较灵活,但可能会导致接口膨胀。 - **Type2设值注入**:通过setter方法来注入依赖项。这种方式简单直观,易于理解和实现。 - **Type3构造子注入...
- **Abstract Factory 模式**:此模式提供了一个接口来创建一系列相关的或相互依赖的对象,而无需指定它们具体的类。 - **Singleton 模式**:确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于控制对...
这些文件可能包含每个模块的功能概述、预期用途、依赖关系,以及可能的接口设计。为了进一步开发这些模块,开发者需要考虑以下几点: 1. **模块功能**:明确每个模块的核心功能,确保它们能够独立完成特定任务。 2....
- **依赖注入**:通过依赖注入,框架可以帮助管理对象间的依赖关系,降低耦合度。 - **可扩展性**:设计时考虑了插件或自定义模块的添加,使得框架能够适应不同的业务需求。 3. **学习FrameWorkSrc源码的重要性**...
### UML for Java ...- **接口隔离原则 (ISP)**:客户端不应该被强制依赖它不使用的接口。 这些原则对于创建灵活、可维护的软件系统至关重要。遵循这些原则可以减少代码的耦合度,提高系统的可扩展性和可复用性。
3. **抽象工厂模式(Abstract Factory)**:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。 4. **建造者模式(Builder)**:将一个复杂对象的构建与其表示分离,使得同样的构建过程可以...
例如,Spring框架利用反射来实现依赖注入,使得组件之间的耦合度大大降低。 3. **单元测试**:反射可以用来访问私有成员,这对于编写单元测试非常有用,因为它允许测试人员直接访问和修改类的内部状态,从而更全面...
- **接口隔离原则**(ISP): 使用者不应该被强制依赖于它不需要的方法。 #### 七、dX实践 - **迭代开发** - **初始探索**: 快速了解项目范围。 - **功能特征评估**: 确定项目的主要特性和功能。 - **探究**: ...
众筹系统是一种在线平台,它允许个人或企业向公众发起项目,寻求资金支持,以实现创新想法或商业计划。在这个基于Java实现的众筹系统中,我们将探讨其核心技术栈、设计原则以及核心功能的实现。 一、核心技术栈 1....
- 模块间明确接口减少耦合度。 - **面向对象编程原则:** - 遵循SOLID原则。 - 使用继承和多态提高代码灵活性。 **3. 如何进行实验** - **定义实验目标:** - 明确希望通过实验验证什么假设。 - **设计实验...
- **依赖关系**:描述了包之间的关系,有助于管理和控制代码间的耦合度。 - **二进制组件(.jar文件)**:用于封装编译后的Java类库,便于重用和部署。 以上是根据《UML for Java Programmers》中文版的部分内容整理...
- 观察者模式实现了对象之间的松耦合,当一个对象的状态改变时,所有依赖它的对象都会得到通知并自动更新。 UML图在描述软件行为时,如顺序图用于描述用例的步骤序列,通过生命线和消息传递来表示对象间的交互。而...
全局性相关(如控制依赖)可能造成流水线阻塞,解决方法包括猜想法、提前形成条件码和设置相关专用通路。拆分瓶颈子过程和并联瓶颈子过程是消除速度瓶颈的常见策略。 6. 浮点运算:浮点数下溢处理有多种方法,如...
- **最小化依赖**:利用抽象基类等方式减少不同模块之间的耦合度。 - **里氏替换原则**:确保派生类可以替换其基类而不影响程序的正确性。 - **开闭原则**:软件实体应该是可扩展的但不可修改的。这意味着当需求变化...
DAAB),它抽象了数据库访问层,使开发者可以使用统一的接口来处理不同的数据库系统,如SQL Server、Oracle、MySQL等,降低了与特定数据库API的耦合度。 2. **日志应用块**:日志应用块(Logging Application ...
Spring的核心特性包括依赖注入(DI)、面向切面编程(AOP)等,这些特性有助于开发者构建松耦合、易于测试的应用程序。 #### Spring MVC框架 Spring MVC是Spring框架的一个模块,专门用于Web应用开发。它提供了一种...
依赖注入则可以帮助代码保持松散耦合,提高可测试性和可维护性。 尽管Leao Framework以“纯娱乐”为定位,但它依然体现了Ruby语言的优雅和灵活性。对于初学者来说,这是一个很好的实践平台,可以深入理解Web开发的...