耦合到底是什么?
耦合(或者称为依赖)是程序模块之间的依赖程度。
从定义上看,耦合和内聚是相反的:
内聚关注模块内部的元素的结合程度
耦合关注模块之间的依赖程度
理解耦合的关键有两点:
什么是模块?
模块和内聚里面提到的模块是一样的,耦合中的模块其实也是可大可小的。
常见的模块有函数,类,包,子模块,子系统等
什么是依赖?
依赖
这个词很好理解,通俗地讲,就是某个模块用到了另一个模块的一些元素,元素可大可小。比如一个类的元素有成员变量,方法,静态变量等
例如, A 类使用了 B 类作为参数,在 A 类的函数中使用了 B 类来完成某些功能...
耦合的分类 ?
参考维基百科,耦合有 8 种,以下各种形式的耦合,其耦合程度越来越高
1 无耦合
无耦合意味着模块之间没有任何关联
或者交互
有的人认为无耦合
就是最好的,但其实不然。完成没有耦合意味着一个模块完全不依赖任何其它模块
如果这个模块是非常底层的模块,那么没有什么问题
但如果这个模块完全自给自足,什么事情都自己做,那么就得不偿失了。
完全无耦合的模块虽然不受其它模块的影响,但同时也失去了重用其它模块的机会
每个轮子都要自己发明,这样的效率低下。
就像一个人与世隔绝一样,什么东西都要自给自足,不能利用分工合作。
例如,我们要设计一个学生管理的类 StudentManager
,在正常情况下这样的类一般会依赖 数据库操作 DAO类
, 日志记录Logger类
但其实我们完全可以设计一个无耦合的 StudentManager
类,直接使用 JDBC
操作数据库,使用文件 API 记录日志。
样例代码如下:
/**
* 完全无耦合的类的设计
* 使用 JDBC 操作数据库,使用文件 API 记录日志
* 只以 add 操作为例,其它的 update,delete,get操作类似
* 欢迎关注 HelloWorld 开发者社区 【 www.helloworld.net 】
*/
public class StudentManager {
public void add(int studentId, String name){
//使用自己的私有方法操作数据库
dbInsert(studentId,name);
//使用自己的私有方法记录日志
log("ERROR","add a student");
}
//不使用DAO类,直接用JDBC操作数据库
private void dbInsert(int studentId, String name){
//省略具体代码实现
}
//不使用log4j之类的日志库,自己打开日志文件写日志
private void log(String level,String message){
//省略具体代码实现
}
}
2 消息耦合
消息耦合,即模块之间的耦合关系表现在消息传递上。如下图
这里的消息
随着模块的不同而不同。
例如:
系统与子系统 : 两个系统的交互接口,比如 HTTP 接口,Java RPC 接口等
类与类的消息:比如 A 类的方法调用了B 类的某个方法,这个方法就是消息
消息耦合是一种耦合程度很低的耦合,是比较理想的耦合,因为调用方仅仅依赖被调用方的消息
即不需要传递参数,也不需要了解被调用方的内部逻辑,更不需要控制调用方内部的逻辑
我们以 人开车
这个场景为例,代码如下:
public class Car {
public void drive(){
//省略具体实现
}
}
public class Person {
public void driveCar(Car car){
car.drive(); //消息耦合
}
}
3 数据耦合
两个模块间通过参数传递基本数据,称为数据耦合
如下图:
这里有 2 点需要特别关注,这也是数据耦合区别于其它耦合类型的关键特征
- 通过参数传递,而不是通过全局数据,配置文件 ,共享内存等其它方式
- 传递的参数是基本数据类型,而不是数据结构,基本类型有:比如在Java中,有 Integer , double , String等类型
在下例例子中, Teacher 类和 Student 类的耦合就是数据耦合
public class Student {
/**
* 学号
* 这就是数据耦合的地方
*/
public int studentId;
public String getName(int studentId){
//根据 studentId查询数据库,获取学生的姓名,具体代码省略
String name = "待兔";
return name;
}
public int getRank(int studentId){
//根据 studentId查询数据库,获取学生的排名,具体代码省略
int rank = 1;
return rank;
}
}
public class Teacher {
public void printStudentRank(int studentId){
Student student = new Student();
// Teacher 依赖 Student 类,通过参数传递类型为 int 的基础数据 studentId
String name = student.getName(studentId);
int rank = student.getRank(studentId);
System.out.println("姓名:" + name + " 排名:" + rank);
}
}
4 数据结构耦合
两个模块通过传递数据结构的方式传递数据,称为数据结构耦合,又称为标签耦合。
个人觉得数据结构耦合
能更好的理解意思
如下图:
数据结构耦合
和 数据耦合
是比较相近的,主要差别在于数据结构耦合
中传递的不是 基本数据
, 而是数据结构
另外需要注意的是:数据数据中的成员数据并不是每一个都用到,可以只用其中一部分。
在如下样例中, Teacher
类和 Student
类的耦合就是数据结构耦合,且只用到了 StudentInfo.id
这个字段
代码示例如下:
public class StudentInfo {
public String name;
public int id;
public int rank;
}
public class Student {
/**
* @param info 学生信息,这里就是数据耦合的地方
* @return
*/
public String getName(StudentInfo info){
//只用到了 id ,StudentInfo其它的字段并没有用到
return getNameById(info.id);
}
/**
* @param info 学生信息,这里就是数据耦合的地方
* @return
*/
public int getRank(StudentInfo info){
//只用到了 id ,StudentInfo其它的字段并没有用到
return getRankById(info.id);
}
private String getNameById(int studentId){
String name = "tom";
//查询数据库,获取学生姓名,这里省略具体实现
return name;
}
private int getRankById(int studentId){
int rank = 1;
//查询数据库,获取学生排名,这里省略具体实现
return rank;
}
}
5 控制耦合
当一个模块可以通过某种方式控制另一个模块的行为时,称为控制耦合
如下图:
是常见的控制方式就是通过传入一个控制参数来控制函数的处理流程或者输出,例如常见的工厂类
/**
* 生产手机的工厂类
*/
public class PhoneFactory {
public String producePhone(int type){
if (type == 100) {
return "小米手机";
} else if (type == 200) {
return "华为手机";
} else if (type == 300) {
return "苹果手机";
} else {
return "其它手机";
}
}
}
6 外部耦合
当两个模块依赖相同的外部数据格式,通信协议,设备接口时,称为外部耦合
如下图:
理解外部耦合的关键在于:为什么叫 “外部” ?
这里的外部
当然是与 内部
相对应的,比如前面我们提到的各种耦合方式,可以都认为是内部耦合
因为这些耦合
都是由模块内部来完成的。但是在外部耦合
的场景下,两个模块对其它模块没有直接的感知
两个模块之间也没有直接的交互,而是通过约定的协议
, 格式
,接口
等完成分工合作
典型的外部耦合,其实我们时时都能看到,那就是操作系统和外设之间的耦合 。
例如,操作系统和 USB鼠标,这里的 USB
就是一个标准接口,操作系统接收 USB 的输入信号
而鼠标按照 USB 的接口的输出信号 ,如下图
7 全局耦合
当两个模块共享相同的全局数据时,称为全局耦合
如下图:
全局耦合是一种比较常见的耦合方式,例如 C/C++程序的全局变量,JAVA中的单例变量等
8 内容耦合
当一个模块依赖另一个模块的内部内容时,称为内容耦合
内容耦合是最差的一种耦合方式,因此它有一个形象的名称:病态耦合
内容耦合是最差的一种耦合方式,因为内容耦合完全破坏了模块的封装性。
处于内容耦合的两个模块,就像是一条绳上的蚂蚱,没有办法单独修改或者优化
如上图中所示:B模块想要修改内容,是不能独立修改的,因为修改会影响 A 模块
即使改个名称也要一起改
最常见的内容耦合的例子就是某个类有一个public的成员变量,然后其它类直接使用这个成员变量
示例代码如下:
public class Victim { public int money = 100; //其它类可以直接访问这个"内容",包括 "小偷" 类 Thief public void makeMoney(int m){ money += m; }}
public class Thief { public void stolenMoney(Victim victim){ victim.money -= 100; //内容耦合,小偷可以直接将钱 减去 100 }}
在上面这个代码样例中,如果 Victim
类想将 money
改名为 myMoney
,那么是不能单独修改的,Thief类需要同步修改才可以。
小结
上以就是相关耦合的内容,我们的目的是追求低耦合。下面对本章的内容小小的总结一下:
耦合(或者称为依赖)是程序模块之间的依赖程度。
从定义上看,耦合和内聚是相反的:
内聚关注模块内部的元素的结合程度
耦合关注模块之间的依赖程度
理解耦合的关键有两点:
什么是模块?
模块和内聚里面提到的模块是一样的,耦合中的模块其实也是可大可小的。
常见的模块有函数,类,包,子模块,子系统等
什么是依赖?
依赖
这个词很好理解,通俗地讲,就是某个模块用到了另一个模块的一些元素,元素可大可小。比如一个类的元素有成员变量,方法,静态变量等
例如, A 类使用了 B 类作为参数,在 A 类的函数中使用了 B 类来完成某些功能...
耦合有以下几种分类:
- 无耦合
- 消息耦合
- 数据耦合
- 数据结构耦合
- 控制耦合
- 外部耦合
- 全局耦合
- 内容耦合