程序耦合分析
耦合概念
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。 在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
耦合的分类
非直接耦合(Nondirect Coupling
)
如果两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的,这就是非直接耦合。这种耦合的模块独立性最强。
数据耦合(Data Coupling
)
如果一个模块访问另一个模块时,彼此之间是通过数据参数(不是控制参数、公共数据结构或外部变量)来交换输入、输出信息的,则称这种耦合为数据耦合。由于限制了只通过参数表传递数据,按数据耦合开发的程序界面简单、安全可靠。因此,数据耦合是松散的耦合,模块之间的独立性比较强。在软件程序结构中至少必须有这类耦合。
印记耦合(Stamp Coupling
)
如果一组模块通过参数表传递记录信息,就是标记耦合。事实上,这组模块共享了这个记录,它是某一数据结构的子结构,而不是简单变量。这要求这些模块都必须清楚该记录的结构,并按结构要求对此记录进行操作。在设计中应尽量避免这种耦合,它使在数据结构上的操作复杂化了。如果采取“信息隐蔽”的方法,把在数据结构上的操作全部集中。
控制耦合(Control Coupling
)
如果一个模块通过传送开关、标志、名字等控制信息,明显地控制选择另一模块的功能,就是控制耦合。这种耦合的实质是在单一接口上选择多功能模块中的某项功能。因此,对所控制模块的任何修改,都会影响控制模块。另外,控制耦合也意味着控制模块必须知道所控制模块内部的一些逻辑关系,这些都会降低模块的独立性。
外部耦合(External Coupling
)
一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。例如C语言程序中各个模块都访问被说明为extern类型的外部变量。外部耦合引起的问题类似于公共耦合,区别在于在外部耦合中不存在依赖于一个数据结构内部各项的物理安排。
公共耦合(Common Coupling
)
若一组模块都访问同一个公共数据环境,则它们之间的耦合就称为公共耦合。公共的数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。 这种耦合会引起下列问题:
所有公共耦合模块都与某一个公共数据环境内部各项的物理安排有关,若修改某个数据的大小,将会影响到所有的模块。
无法控制各个模块对公共数据的存取,严重影响软件模块的可靠性和适应性。
公共数据名的使用,明显降低了程序的可读性。
公共耦合的复杂程度随耦合模块的个数增加而显著增加。若只是两个模块之间有公共数据环境,则公共耦合有两种情况。
若一个模块只是往公共数据环境里传送数据,而另一个模块只是从公共数据环境中取数据,则这种公共耦合叫做松散公共耦合。若两个模块都从公共数据环境中取数据,又都向公共数据环境里送数据,则这种公共耦合叫做紧密公共耦合。只有在模块之间共享的数据很多,且通过参数表传递不方便时,才使用公共耦合。否则,还是使用模块独立性比较高的数据耦合好些。
内容耦合(Content Coupling
)
如果发生下列情形,两个模块之间就发生了内容耦合。
- 一个模块直接访问另一个模块的内部数据;一个模块不通过正常入口转到另一模块内部;两个模块有一部分程序代码重叠(只可能出现在汇编语言中);
- 一个模块有多个入口。在内容耦合的情形,所访问模块的任何变更,或者用不同的编译器对它再编译,都会造成程序出错。好在大多数高级程序设计语言已经设计成不允许出现内容耦合。它一般出现在汇编语言程序中。这种耦合是模块独立性最弱的耦合。
解耦
降低程序间的依赖关系
实际开发中应做到编译期不依赖, 运行时才依赖
例如:
JDBC注册驱动时的高耦合:
//1.注册JDBC驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
编译时就必须依赖com.mysql.cj.jdbc.Driver()
的 jar
包才能通过编译, 否则就报错, 无法运行.
JDBC的解耦实现低耦合:
//解耦
Class.forName("com.mysql.cj.jdbc.Driver");
如果没有com.mysql.cj.jdbc.Driver
的 jar
包也能编译成功, 运行时只会抛出 ClassNotFoundException
异常.
类之间的解耦思路
- 使用反射来创建对象, 而避免使用
new
关键字 - 通过读取配置文件来获取要创建的对象全限定类名
工厂模式(单例)
/**
* @author Harlan
* @date 2020/8/13 18:02
* 一个创建Bean对象的工厂
* Bean 在计算机语言中, 有可重用组件的含义.
* JavaBean 用Java语言编写的可重用组件.
* 工厂用于创建service和dao对象
* 需要配置文件
* 唯一标识 = 全限定类名
* 通过反射创建对象
*/
public class BeanFactory {
private static Properties props;
/**
* 定义一个Map用于存放我们要创建的对象, 既为容器
*/
private static Map<String, Object> beans;
static {
//加载配置文件
props = new Properties();
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
props.load(in);
} catch (IOException e) {
e.printStackTrace();
}
//实例化容器
beans = new HashMap<String, Object>();
//取出配置文件中所有的Key
Enumeration<Object> keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
try {
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器
beans.put(key, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 根据名称获取对象
* @param beanName 名称
* @return 对象
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}