一、程序的耦合

耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差(降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。

1、耦合的定义

在软件工程中,耦合指的就是就是模块之间的依赖性。模块之间的耦合越高,维护成本越高。因此模块的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准,划分模块的一个准则就是高内聚低耦合。

2、耦合的分类

(1)内容耦合

当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。

(2)公共耦合

两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。

(3)外部耦合

一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。

(4)控制耦合

一个模块通过接口向另一个模块传递-一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。

(5)标记耦合

若一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和c之间存在一个标记耦合。

(6) 数据耦合

模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。

(7)非直接耦合

两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。

总结

耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:

如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合

3、内聚与耦合

所谓内聚,是指机能相关的程序组合成一个模块的程度,或是各机能凝聚的状态或程度,是结构化分析的重要概念之一。内聚包括:偶然内聚、逻辑内聚、时间内聚、过程内聚、通信内聚、顺序内聚和功能内聚。最好的是功能内聚,最差的是偶然内聚。

内聚是一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。

内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他
模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合

二、降低程序的耦合度

软件设计的标准要求我们降低程序模块之间的耦合度,那么在实际开发中该如何降低模块之间的耦合度呢?下面将介绍两种降低程序模块之间耦合度的方法。

1、使用工厂模式(Factory Method)

工厂模式是最常用的实例化对象模式,是用工厂方法代替new操作的一种模式。下面通过一个例子来介绍如何使用工厂模式降低程序的耦合度。

假设有一个银行账户管理系统,该系统中有一个IAccountSerive接口及其实现类AccountServiceImpl用来提供账户操作的相关服务,同时还有一个数据库访问对象接口IAccountDao及其实现类AccountDaoImpl。如果使用传统方法编写程序,就会采用如下写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.frankfang.bank.service.impl;

import cn.frankfang.bank.dao.IAccountDao;
import cn.frankfang.bank.dao.impl.AccountDaoImpl;
import cn.frankfang.bank.service.IAccountService;

/**
* 服务层接口的实现类
*/
public class AccountServiceImpl implements IAccountService {
// 数据库访问对象接口
private IAccountDao accountDao = new AccountDaoImpl();

// 实现接口中的方法
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}

调用IAccountService接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.frankfang.bank;

import cn.frankfang.bank.service.IAccountService;
import cn.frankfang.bank.service.impl.AccountServiceImpl;

public class Application {
public static void main(String[] args) {
// 实例化服务层对象实现类
IAccountService accountService = new AccountServiceImpl();
// 调用服务层方法
accountService.saveAccount();
}
}

上述代码虽然可以实现功能,但存在一定的问题:当需要调用服务层接口时必须要实例化一个接口的实现类,而该接口的实现类又需要调用数据访问层的接口,这样在实现类中不得不再实例化一个数据访问层接口的实现类,这表明不同模块之间存在较高的耦合度,这显然是不符合软件设计规范的。为了解决这个问题,我们可以采用工厂模式来降低程序之间的耦合度。

工厂模式具体实现起来也很简单,只需编写一个专门用来生产所需对象的工厂类即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.frankfang.bank.factory;

/**
* Bean工厂
*/
public class BeanFactory {

/**
* 获取实例化的对象
* @param className 所需对象的全类名
* @return 实例化的对象
*/
public static Object getBean(String className) {
try {
return Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

在有了工厂之后,之前的业务代码就可以改为以下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package cn.frankfang.bank.service.impl;

import cn.frankfang.bank.dao.IAccountDao;
import cn.frankfang.bank.factory.BeanFactory;
import cn.frankfang.bank.service.IAccountService;

/**
* 服务层接口的实现类
*/
public class AccountServiceImpl implements IAccountService {
// 数据库访问对象接口
private IAccountDao accountDao = (IAccountDao) BeanFactory
.getBean("cn.frankfang.bank.dao.impl.AccountDaoImpl");

// 实现接口中的方法
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}

调用IAccountService接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.frankfang.bank;

import cn.frankfang.bank.factory.BeanFactory;
import cn.frankfang.bank.service.IAccountService;

public class Application {
public static void main(String[] args) {
// 实例化服务层对象实现类
IAccountService accountService = (IAccountService) BeanFactory
.getBean("cn.frankfang.bank.impl.AccountServiceImpl");
// 调用服务层方法
accountService.saveAccount();
}
}

通过上述例子可以发现,使用工厂模式设计软件可以降低模块之间的耦合度。上述的例子还可以继续改进:可以将需要实例化的对象的全类名写在配置文件中,在需要工厂生产实例化对象时读取配置文件获取全类名,之后将全类名传给工厂进行生产,这样又进一步降低了耦合度。

在实际开发中我们可以把所有需要实例化对象使用配置文件进行配置,当启动服务时通过工厂中的方法读取配置文件,将这些对象创建出来并保存起来,当程序需要使用对象的时候,直接从工厂获取即可。

2、控制反转(Inversion of Control)

控制反转(Inversion of Control)是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,DI),还有一种方式叫依赖查找(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它,也可以说依赖被注入到对象中。

那么这种方法为什么叫做控制反转呢?下面通过几张图来说明:

(1)使用传统方法获取资源

使用传统方法获取资源

当我们使用传统方法获取资源时都是采用new的方法来实例化对象,获取资源的方式是主动的

(2)使用依赖注入方式获取资源

使用依赖注入方式获取资源

当我们通过依赖注入方式获取资源时,资源是通容器注入到对象中的,这种获取资源的方式是被动的,也就是将实例化对象的控制权交给了工厂。这种被动接受的方式获取对象的思想就是控制反转。

采用依赖注入技术之后,A 的代码只需要定义一个私有的 B 对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到 A 类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定,这就是Spring框架实现控制反转的基本思路,下面我们将介绍Spring中的IoC。

三、Spring中的IoC

1、Spring IoC容器

在Spring框架中,org.springframework.beansorg.springframework.context包是Spring框架的IoC容器的基础。其中 BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象。 ApplicationContextBeanFactory的子接口。它增加了更易于与Spring框架中的AOP进行整合的特性、消息资源处理(用于国际化)、事件发布和应用层特定上下文(如用于web应用程序的WebApplicationContext)的集成。

简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext增加了更多针对企业的功能。ApplicationContextBeanFactory的超集。下图展示了相关接口和实现类的关系:

Spring IoC容器相关接口和实现类

BeanFactoryApplicationContext在创建对象的时间上存在差别:

  • ApplicationContext:只要读取到配置文件,默认情况下就会创建对象
  • BeanFactory:当需要使用对象时才会创建对象

在实际项目中通常会选择使用ApplicatonContext,因为创建对象的过程只会在服务启动时进行,这样在应用运行过程中就无需再次创建对象,调用相关服务时耗费的时间会更短。

2、bean

在Spring框架中,构成应用程序主干并由Spring IoC容器管理的对象称为beanbean是由Spring IoC容器实例化、组装和管理的对象。否则,bean只是应用程序中许多对象中的一个。bean以及它们之间的依赖关系反映在容器使用的配置元数据中。

若需获取更多关于Spring IoC容器和Bean的相关信息,请参阅:Spring Framework Core Technologies