`

关于接口、依赖、耦合,我的一些想法

阅读更多
开发人员经常提到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)的方式来进行设计。这种方式可以让组件之间的依赖关系更加清晰,易于管理和扩展。 - **依赖注入**是一种软件...

    spring开发指南

    - **Type1接口注入**:通过定义接口的方法来注入依赖项。这种方式比较灵活,但可能会导致接口膨胀。 - **Type2设值注入**:通过setter方法来注入依赖项。这种方式简单直观,易于理解和实现。 - **Type3构造子注入...

    软件工程师常用的经典设计模式

    - **Abstract Factory 模式**:此模式提供了一个接口来创建一系列相关的或相互依赖的对象,而无需指定它们具体的类。 - **Singleton 模式**:确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于控制对...

    IDEAS-FOR-MODULES:我想构建的模块的想法。 这些模块只是我想构建的模块的占位符,直到它们被移出这个存储库

    这些文件可能包含每个模块的功能概述、预期用途、依赖关系,以及可能的接口设计。为了进一步开发这些模块,开发者需要考虑以下几点: 1. **模块功能**:明确每个模块的核心功能,确保它们能够独立完成特定任务。 2....

    FrameWorkSrc源码

    - **依赖注入**:通过依赖注入,框架可以帮助管理对象间的依赖关系,降低耦合度。 - **可扩展性**:设计时考虑了插件或自定义模块的添加,使得框架能够适应不同的业务需求。 3. **学习FrameWorkSrc源码的重要性**...

    《UML for Java Programmers》学习笔记

    ### UML for Java ...- **接口隔离原则 (ISP)**:客户端不应该被强制依赖它不使用的接口。 这些原则对于创建灵活、可维护的软件系统至关重要。遵循这些原则可以减少代码的耦合度,提高系统的可扩展性和可复用性。

    C#设计模式源码

    3. **抽象工厂模式(Abstract Factory)**:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。 4. **建造者模式(Builder)**:将一个复杂对象的构建与其表示分离,使得同样的构建过程可以...

    Manning - Java Reflection in Action.pdf

    例如,Spring框架利用反射来实现依赖注入,使得组件之间的耦合度大大降低。 3. **单元测试**:反射可以用来访问私有成员,这对于编写单元测试非常有用,因为它允许测试人员直接访问和修改类的内部状态,从而更全面...

    UML for Java Programmers中文版.pdf

    - **接口隔离原则**(ISP): 使用者不应该被强制依赖于它不需要的方法。 #### 七、dX实践 - **迭代开发** - **初始探索**: 快速了解项目范围。 - **功能特征评估**: 确定项目的主要特性和功能。 - **探究**: ...

    基于Java实现的众筹系统

    众筹系统是一种在线平台,它允许个人或企业向公众发起项目,寻求资金支持,以实现创新想法或商业计划。在这个基于Java实现的众筹系统中,我们将探讨其核心技术栈、设计原则以及核心功能的实现。 一、核心技术栈 1....

    how-to-be-a-programmer-cn.pdf

    - 模块间明确接口减少耦合度。 - **面向对象编程原则:** - 遵循SOLID原则。 - 使用继承和多态提高代码灵活性。 **3. 如何进行实验** - **定义实验目标:** - 明确希望通过实验验证什么假设。 - **设计实验...

    UML for Java Programmers中文版

    - **依赖关系**:描述了包之间的关系,有助于管理和控制代码间的耦合度。 - **二进制组件(.jar文件)**:用于封装编译后的Java类库,便于重用和部署。 以上是根据《UML for Java Programmers》中文版的部分内容整理...

    软件架构复习资料1.doc

    - 观察者模式实现了对象之间的松耦合,当一个对象的状态改变时,所有依赖它的对象都会得到通知并自动更新。 UML图在描述软件行为时,如顺序图用于描述用例的步骤序列,通过生命线和消息传递来表示对象间的交互。而...

    计算机系统结构期末考试题及其答案.doc

    全局性相关(如控制依赖)可能造成流水线阻塞,解决方法包括猜想法、提前形成条件码和设置相关专用通路。拆分瓶颈子过程和并联瓶颈子过程是消除速度瓶颈的常见策略。 6. 浮点运算:浮点数下溢处理有多种方法,如...

    C++ Coding Standard

    - **最小化依赖**:利用抽象基类等方式减少不同模块之间的耦合度。 - **里氏替换原则**:确保派生类可以替换其基类而不影响程序的正确性。 - **开闭原则**:软件实体应该是可扩展的但不可修改的。这意味着当需求变化...

    EnterpriseLibrary学习

    DAAB),它抽象了数据库访问层,使开发者可以使用统一的接口来处理不同的数据库系统,如SQL Server、Oracle、MySQL等,降低了与特定数据库API的耦合度。 2. **日志应用块**:日志应用块(Logging Application ...

    JavaEE源码基于BS架构微博源码数据库sql文档

    Spring的核心特性包括依赖注入(DI)、面向切面编程(AOP)等,这些特性有助于开发者构建松耦合、易于测试的应用程序。 #### Spring MVC框架 Spring MVC是Spring框架的一个模块,专门用于Web应用开发。它提供了一种...

    LeaoFramework:纯娱乐

    依赖注入则可以帮助代码保持松散耦合,提高可测试性和可维护性。 尽管Leao Framework以“纯娱乐”为定位,但它依然体现了Ruby语言的优雅和灵活性。对于初学者来说,这是一个很好的实践平台,可以深入理解Web开发的...

Global site tag (gtag.js) - Google Analytics