耦合(六种)与 内聚(七种)—《软件工程与计算》笔记

耦合(六种)与 内聚(七种)

1. 耦合(六种)

耦合描述的是 两个模块 之间关系的复杂程度。耦合性越低越好,耦合度越高,模块划分越差,越不利于软件变更和复用。根据耦合性的高低依次分为以下六种耦合:

名称概述判断依据修改意见
内容耦合一个模块直接修改或者依赖于另一个模块的内容含有 goto/alter 语句
模块A 修改了 模块B 传来的数据
修改属性没有通过 get/set 方法
访问集合变量没有使用迭代器
去掉不该有的语句/加上缺少的变量或方法,更改代码保持语义不变。
公共耦合模块之间共享全局的数据。共享的是变量两个或多个模块共享全局变量将需要的全局共享变量变成方法参数传入
重复耦合模块之间有同样逻辑的重复代码一段业务逻辑在两个地方被调用却没有写在一个方法内,即明显两个地方的代码有极高的重复。创建一个方法,把重复出现的业务逻辑写进方法,通过调用方法传入不同的属性值实现和原来一样的业务逻辑
控制耦合一个模块给另一个模块传递控制信息。共享的是逻辑。传递的信息既有数据,还有逻辑(如果只传递数据则为数据耦合),两个模块都需要知道所传递的信息的逻辑含有 swith-caseif-else-if-elsecommandtypeflag 语句的,比较容易是控制耦合。
印记耦合共享一个数据类型,但是却只用了其中一部分。共享的是数据结构所传及所需。
数据耦合两个模块的所有参数是同类型的数据项。共享的是数据。所有数据全被用到
使用所传引用对象的方法
一个模块给另一个模块传入的参数得到输出
  • 内容、重复、公共耦合是不能接受的
  • 控制、印记耦合是可以接受的
  • 数据耦合是最好的

2. 内聚(七种)

内聚表达的是 一个模块 内部的联系的紧密性。内聚性越高越好,越低越不易实现变更和复用。内聚性由低到高依次分为以下七种内聚:

名称概述判断依据修改意见
偶然内聚模块执行多个完全不相关的操作将不相关的操作单独封装
逻辑内聚模块执行一系列相关操作,每个操作的调用由其他模块来决定这些操作逻辑上相似但没有直接关联,表现为 并立的 分支选择语句,即 swith-caseif-else-if-else (多个条件选择语句中最多只会有一个分支执行)将并立的操作单独封装
时间内聚模块执行一系列与时间有关的操作(相同的时间)
这些操作在同一时间段内发生
init() 初始化操作
c++ 构造函数、析构函数
将每个初始化模块单独封装
过程内聚模块执行一些与步骤顺序有关的操作这些操作是解决同一个问题的不同步骤,表现为这些操作的顺序不能颠倒
通信内聚模块执行一系列与步骤有关的操作,并且这些操作在相同的数据上进行。(相同的数据)注意是同一数据,有两种类型
- 方法间通信内聚(在多个方法中共享对象,这里,共享不当容易形成公共耦合)
- 方法内通信内聚(在方法中多次调用形参的不同属性)
功能内聚模块只执行一个操作或达到一个单一目的。(完全以功能(行为)为依据进行模块划分,通常是函数与过程)单一的目的 即指一个类内的各属性是否体现一个职责
或者各属性之间可否抽象
信息内聚模块进行许多操作,各个都有各自的入口点,每个操作的代码相对独立,而且所有操作都在相同的数据结构上完成。(以数据与功能间的相互支撑为依据进行模块分解)接口或抽象类通常是信息内聚的。
  • 偶然内聚、逻辑内聚是不能接受的
  • 时间、过程、通信内聚是可以接受不可避免的(一般控制器模块会是这三种内聚)
  • 功能内聚、信息内聚是最好的(执行具体功能的模块一般是这两种内聚中的一种或两种)

3. 例子

3.1 例1

3.1.1 问题

下面的 gcd 方法内部的代码是哪种类型的内聚?

int gcd(int p, int q) {
	int r;
	while(p != 0) {
		int r = p;
		p = q % p;
		q = r;
	}
}
3.1.2 解析

按照最大公约数的计算方式,完成 pq 的转换,模块达到了一个单一的目的,是功能内聚。

功能内聚强调目标与需求相对应

3.2 例2

3.2.1 问题

下面的 validate_checkout_request 方法的内部代码是哪种类型的内聚?

void validate_checkout_request(input_form i) {
	if(!(i.name.size()>4 && i.name.size()<20)) {
		error_message("Invalid name");
	}
	if(!(i.date.month>=1 && i.date.month<=12)) {
		error_message("Invalid month");
	}
}
3.2.2 解析

分析一:两个 if 判断可能都会为 true 并执行其后语句(非并立),即这两个操作是相关联的,故不是逻辑内聚。在方法中多次调用了形参 i 的不同属性,模块执行的操作是在相同的数据上进行的,故为方法内通信内聚。综上,本题是通信内聚。

分析二:本方法旨在判断输入值是否有效,模块达到一个单一的目的。所以如果需求与方法实现的目标一致(但是本题没有给出需求),也可以是功能内聚。

3.3 例3

3.3.1 问题

下面的 validate_checkout_request 方法的内部代码是哪种类型的内聚?validate_checkout_request 方法与 valid_month 方法之间是哪种类型的耦合?

void validate_checkout_request(input_form i) {
	if(!valid_string(i.name)) {
		error_message("Invalid name");
	}
	if(!valid_month(i)) {
		error_message("Invalid month");
	}
	int valid_month(input_form i) {
		return i.date.month>=1 && i.date.month<=12;
	}
}
3.3.2 解析
  • validate_checkout_request 方法中两个 if 判断语句非并立分支(即并非毫不相关),故排除逻辑内聚。两个判断语句中使用了形参 i 的不同属性,即模块执行的操作在相同的数据上进行,是通信内聚。
  • 第一个模块给第二个模块传递 date 数据,第二个模块只用了其中的一部分,故为印记标记。(因为 valid_month 没有修改 d 的值,故排除内容耦合。)

印记耦合改进后为数据耦合(所有传递的属性全被用到):

void validate_checkout_request(input_form i) {
	if(!valid_string(i.name)) {
		error_message("Invalid name");
	}
	if(!valid_month(i.date.month)) {
		error_message("Invalid month");
	}
}

int valid_month(int month) {
	return month>=1 && month<=12;
}

3.4 例4

3.4.1 问题

下面的 validate_checkout_requst 方法与 valid 方法之间是哪种类型的耦合?

void validate_checkout_request(input_form i) {
	if(!valid(i.name.STRING)) {
		error_message("Invalid name");
	}
	if(!valid(i.date.DATE)) {
		error_message("Invalid month");
	}
}

int valid(String s, int type) {
	swith(type) {
		case STRING:
			return strlen(s)<MAX_STRING_SIZE;
		case DATE:
			Date d = parse_date(s);
			return d.month>=1 && d.month<=12;
	}
}
3.4.2 解析

第一个模块给第二个模块传递的信息既有数据,又有逻辑:第一个模块发送给第二个模块的消息是由一个值 i.name/i.date 和数据类型 STRING/DATE 组成,第二个模块也要根据数据类型来判断值是否有效这个逻辑来解析消息。所以两个模块共享的是逻辑,是控制耦合。

同时,第二个模块对接收到的 date 类型的参数,只用了其中的 month 属性,故也为印记耦合。

控制耦合和印记耦合是可以接受的。但也可以进行代码完善,完善后为数据耦合:

void validate_checkout_request(input_form i) {
	if(!valid(i.name.STRING)) {
		error_message("Invalid name");
	}
	if(!valid(i.date.DATE)) {
		error_message("Invalid month");
	}
}

int valid_string(String s) {
	return strlen(s)<MAX_STRING_SIZE;
}

int valid_month(int month) {
	return d.month>=1 && d.month<=12;
}

3.5 例5

3.5.1 问题

下面的 validate_checkout_request 方法的内部代码是哪种类型的内聚? validate_checkout_request 方法与 valid_month 方法之间是哪种类型的耦合?

String patron_name, book_name;
Date checkout_date;
void validate_checkout_request(input_form i) {
	patron_name = i.name;
	if(!valid_string()) {
		error_message("Invalid name");
	}
	
	book_name = i.book;
	if(!valid_string()) {
		error_message("Invalid book name");
	}
	
	checkout_date = i.date;
	if(!valid_month()) {
		error_message("Invalid month");
	}
}

int valid_month() {
	return checkout_date.month>=1 && checkout_date.month<=12;
}
3.5.2 解析

validate_checkout_request 方法内部的 if 分支语句可以同时有零个或多个为 true ,故为非并立分支,排除逻辑内聚;模块内三个部分的操作是在相同的数据上进行的(形参的不同属性),故为通信内聚。

validate_checkout_request 方法和 valid_month 方法间,第一个方法中修改了全局变量 checkout_date 的值,第二个方法中使用了 checkout_date 的值,即模块之间共享全局的数据,为公共耦合。改进如下:

String patron_name, book_name;

void validate_checkout_request(input_form i) {
	patron_name = i.name;
	if(!valid_string(i.name)) {
		error_message("Invalid name");
	}
	
	book_name = i.book;
	if(!valid_string(i.book)) {
		error_message("Invalid book name");
	}
	
	Date checkout_date = i.date;
	// 将全局变量变成方法参数传入,消除公共耦合
	if(!valid_month(checkout_date.month)) {
		error_message("Invalid month");
	}
}

int valid_month(int month) {
    return month>=1 && month<=12;
}

// 个人完整性补充,非题目要求
int valid_string(String s) {
    return strlen(s)<MAX_STRING_SIZE;
}

3.6 例6

3.6.1 问题

下列代码是哪种类型的内聚/耦合?

void validate_checkout_request(input_form i) {
	if(!valid_string(i.name)) {
		i.string = "Invalid name";
		error_message();
	}
	if(!valid_string(i.book)) {
		i.string = "Invalid book name";
		error_message();
	}
	valid_month(i.date);
}
void valid_month(Date d) {
	if(d.month < 1) {
		d.month = 1;
	}
	if(d.month > 12) {
		d.month = 12;
	}
	return 1;
}
3.6.2 解析
  • validate_checkout_request 方法内部为通信内聚;
  • valid_month 方法中对 d.month 的修改没有通过 set 方法,故为内容耦合。

改进如下:

void valid_month(Date d) {
	if(d.getMonth() < 1) {
		d.setMonth(1);
	}
	if(d.getMonth() > 12) {
		d.setMonth(12);
	}
	return;
}

3.7 例7

3.7.1 问题

下列代码是哪种类型的内聚/耦合?

void validate_checkout_request(input_form i) {
	int len = 0;
	boolean valid_string = false;
	
	len = i.name.length();
	char arr1[] = new char[len];
	for(char c:arr1) {
		if(c是小写字母) {
			valid_string = true;
		}
	}
	
	if(!valid_string) {
		error_message("Invalid book name");
	}
	
	if(!valid_month(i.date)) {
		error_message("Invalid month");
	}
    
    len = i.book.length();
	char arr2[] = new char[len];
	for(char c:arr2) {
		if(c是小写字母) {
			valid_string = true;
		}
	}
	
	if(!valid_string) {
		error_message("Invalid book name");
	}
	
	if(!valid_month(i.date)) {
		error_message("Invalid month");
	}
}
3.7.2 解析

重复耦合,有相同逻辑的重复代码。

void validate_checkout_request(input_form i) {
	if(!validString(i.name)) {
		error_message("Invalid name");
	}
	if(validString(i.book)) {
		error_message("Invalid book name");
	}
	if(!valid_month(i.date)) {
		error_message("Invalid month");
	}
}

boolean validString(String s) {
	int len = 0;
	boolean valid_string = false;
	len = s.length();
	char arr[] = new char[len];
	for(char c:arr) {
		if(c是小写字母) {
			valid_string = true;
		}
	}
	return valid_string;
}

但是上面这种写法有点问题,arr 并没有被赋值,不过改进还是忠于源代码了,修改一下伪代码大概是这样:

boolean validString(String s) {
	int len = 0;
	boolean valid_string = false;
	len = s.length();
	for(int i=0; i<len; i++) {
		if(s.charAt(i)是小写字母){
			valid_string = true;
		}
	}
	return valid_string;
}

3.8 例8

3.8.1 问题

下面 A 类的 init 方法是哪种类型的内聚?能不能进行改进?怎样改进?

Class A {
	Private:
		FinancialReport fr;
		WeatherDate wd;
		int totalcount;
	Public:
		void init();
}

void init() { // 初始化模块
	// 初始化财务报告
	fr = new (FinancialReport);
	fr.setRatio(5);
	fr.setYear("2010");
	// 初始化当前天气
	w = new(WeatherData);
	w.setCity("NanJing");
	w.setCode("210093");
	// 初始化计算器
	totalCount = 0;
}
3.8.2 解析

init 初始化方法执行的初始化操作都在同一时间段内发生,为时间内聚。

改进,初始化操作单独封装:

public class A {
	Private:
		FinancialReport fr;
		WeatherData wd;
		int totalcount;
	Public:
		void initFianceReport();
		void init WeatherDate();
		void initToalcount();
}

void initFinanceReport() {
	// 初始化财务报告
	fr = new(FinanceReport);
	fr.setRatio(5);
	fr.setYear("2010");
}

void initWeatherData() {
	// 初始化当前天气
	w = new(WeatherData);
	w.setCity("NanJing");
	w.setCode("210093");
}

void initTotalCount() {
	// 初始化计数器
	totalCount = 0;
}

3.9 例9

3.9.1 问题

下列代码是哪种类型的内聚/耦合?

public class Rous {
	public static int findPattern(String text, String pattern) {
		// ...
	}
	public static int average(Vector numbers) {
		// ...
	}
	public static OutputStream openFile(String fileName) {
		// ...
	}
}
3.9.2 解析

偶然内聚。模块执行多个完全不相关的操作(查询、计算、文件操作,从语义上可以看出完全不相关)。

改进,将不相关的操作单独封装:

public class Rous1 {
	public static int findPattern(String text, String pattern) {
		// ...
	}
}

public class Rous2 {
	public static int averager(Vector numbers) {
		// ...
	}
}

public class Rous3 {
	public static OutputStream openFile(String fileName) {
		// ...
	}
}

3.10 例10

3.10.1 问题

下列代码是哪种类型的内聚/耦合?

public void sample(String flag) {
	switch(flag) {
		case ON:
			// ...
			break;
		case OFF:
			// ...
			break;
		case CLOSE:
			// ...
			break;
	}
}
3.10.2 解析

逻辑内聚。(并立的分支语句)

3.11 例11

3.11.1 问题

下列代码是哪种类型的内聚/耦合?

public class foo {
	private String name;
	private int size;
	public void foo() { // 构造函数
		this.name = "Not Set";
		this.size = 12;
	}
	public void ~foo() { // 析构函数
		delete[] name;
		delete size;
	}
}
3.11.2 解析

时间内聚。(构造函数、析构函数通常是时间内聚,记住就好)

3.12 例12

3.12.1 问题

下列代码是哪种类型的内聚/耦合?

void MonthEnd() {
	Report ExR = initExpenseReport();
	Report rr = initRevenueReport();
	Report EmpR = initEmployeeReport();
	
	EmpR.init();
	rr.init();
	ExR.setEmployees(true);
	
	if(ExR.getReportParams()) {
		EmpR.getReportParams();
	}
	
	sendToPrinter(rr);
	sendToPrinter(ExR);
	sendToPrinter(EmpR);
}
3.12.2 解析

过程内聚。

首先,本方法中的消费、收入、雇员并不是完全不相关的(从 if 语句中可以看出),排除偶然内聚;

其次,本方法中没有分支语句,排除逻辑内聚;

于是考虑是否是时间(相同的时间)/ 过程(相同的问题)/ 通信(相同的数据)内聚,可以分析出过程内聚最符合,模块执行一些与步骤顺序有关的操作,这些从左的顺序不能颠倒,先初始化再打印。

3.13 例13

3.13.1 问题

下列代码是哪种类型的内聚/耦合?

public class Calculate {
	public int product;
	public void product(int a, int b) {
		product = a*b;
		// ...
		save(product);
	}
	public void save(int product) {
		// code to store value into database
	}
}
3.13.2 解析

通信内聚。(方法间通信内聚,在多个方法中共享对象)

3.14 例14

3.14.1 问题

下列代码是哪种类型的内聚/耦合?

public int commission(int sale, long percentage) {
	int com;
	// calculate commission
	return com;
}
3.14.2 解析

功能内聚。(方法执行单一的操作,达到单一目的)

3.15 例15

3.15.1 问题

下列代码是哪种类型的内聚/耦合?

public interface Addressee {
	//......
	public abstract String getName();
	public abstract String getAddress();
	//......
}

public class Employee implements Addressee {
	//......
}
3.15.2 解析

信息内聚。(接口、抽象类通常是信息内聚的)

3.16 例16

3.16.1 问题

下列代码是哪种类型的内聚/耦合?

public class Vector3D {
	public int x,y,z;
	//......
}

public class Arch {
	private Vector3D baseline;
	//...
	void slant(int newY) {
		baseline.x = 10;
		baseline.y = 13;
	}
}
3.16.2 解析

内容耦合。(修改属性值没有通过 set 方法)。改进如下:

public class Vertor3D {
	private int x, y, z;
	// get/set 方法
}

public class Arch {
	private Vertor3D baseline;
	//...
	void slant(int newY) {
		baseline.setX(10);
		baseline.setY(13);
	}
}

3.17 例17

3.17.1 问题

下列代码是哪种类型的内聚/耦合?

public routineX(String command) {
	if(command.equals("drawCircle")) {
		drawCircle();
	}
	else {
		drawRectangle();
	}
}
3.17.2 解析

控制耦合。(另一个模块给 routineX 模块传递的 command 是逻辑信息“画圆”,两个模块都需要知道:动词“画”+名词“圆”这个逻辑,才可以完成相应的操作)

3.18 例18

3.18.1 问题

下列代码是哪种类型的内聚/耦合?

public class Employee {
	public String name, emailID;
	//...
}

public class Emailer {
	public void sendEmail(Employee e, String text) {
		//...
	}
	//...
}
3.18.2 解析

印记耦合。共享一个数据结构,却只用了其中一部分。(sendEmail 方法仅用到了 EmployeeemailID

3.19 例19

3.19.1 问题

下列代码是哪种类型的内聚/耦合?

public class Receiver {
	public void message(MyType X) {
		//...
		X.doSomethingForMe(Object data);
		//...
	}
}
3.19.2 解析

数据耦合。(使用所传引用对象的方法通常是数据耦合)

3.20 例20

3.20.1 问题

下列代码是哪种类型的内聚/耦合?

int x;
public class myValue {
	public void addValue(int a) {
		x = x + a;
	}
	public void subtractValue(int a) {
		x = x - a;
	}
}
3.20.2 解析

公共耦合(模块共享全局变量)。改进如下:

public class myValue {
	public int addValue(int a, int x) {
		return x + a;
	}
	public int subtractValue(int a, int x) {
		return x - a;
	}
}

3.21 课本例题

在这里插入图片描述

在这里插入图片描述

  • 28
    点赞
  • 144
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值