面向对象设计原则之 - 低耦合

待兔
• 阅读 1546

耦合到底是什么?

耦合(或者称为依赖)是程序模块之间的依赖程度。

从定义上看,耦合和内聚是相反的:

  • 内聚关注模块内部的元素的结合程度

  • 耦合关注模块之间的依赖程度

理解耦合的关键有两点:

  • 什么是模块?

    模块和内聚里面提到的模块是一样的,耦合中的模块其实也是可大可小的。

    常见的模块有函数,类,包,子模块,子系统等

  • 什么是依赖?

    依赖这个词很好理解,通俗地讲,就是某个模块用到了另一个模块的一些元素,元素可大可小。

    比如一个类的元素有成员变量,方法,静态变量等

    例如, 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 类来完成某些功能...

耦合有以下几种分类:

  • 无耦合
  • 消息耦合
  • 数据耦合
  • 数据结构耦合
  • 控制耦合
  • 外部耦合
  • 全局耦合
  • 内容耦合
点赞
收藏
评论区
推荐文章
待兔 待兔
2年前
面向对象设计原则之 - 高内聚
通常在面向对象设计中,我们经常听到,高内聚,低耦合,那么到底什么是内聚呢?内聚究竟是什么?参考百度百科的解释,内聚的含义如下:内聚(Cohesion),科学名词,是一个模块内部各成分之间相关联程度的度量。我自己的理解是:内聚指一个模块内部元素之间的紧密程度看起来很好理解,但只要深入思考一下,其实没有那么简单。首先,“模块”如何理解?一定会有人说,模块
Wesley13 Wesley13
3年前
DDD领域驱动设计实战
整洁架构、CQRS、六边形架构等微服务架构都旨在“高内聚低耦合”。那DDD分层架构又如何?1DDD分层架构1.1分层架构的基本原则每层只能与位于其下方的层发生耦合。1.2分层架构的分类严格分层架构(StrictLayersArchitect
Stella981 Stella981
3年前
AutoFac
 一、前言  AutoFac是.NET平台下的一款著名的IoCContainer,它可以让我们很轻松的解除项目中服务类的接口与客户类的接口实现类之间的依赖关系,从而降低系统各模块之间耦合程度以提高系统的稳定性。最近在做毕业设计,在开发中采用了autofac来进行依赖注入,这里是对踩到的一些坑的解决方法,希望可以给同样不幸进入这些坑中的童鞋们提供
Wesley13 Wesley13
3年前
Java 类之间的关系
总述类和类之间的关系,耦合度从高到低:is。继承、实现has。组合、聚合、关联use。依赖。要求是:高内聚、低耦合。继承Person和Man之间是继承关系。!(https://oscimg.oschina.net/oscnet/7b9f06e3a37b7bc9c5c2fe14
Wesley13 Wesley13
3年前
(进阶)传统架构和分布式系统架构的优缺点
传统项目:  存在问题:  1:模块之间耦合度太高,其中一个功能升级,其他的模块都得一起升级部署。  2:开发困难,各个团队开发最后都要整合在一起.  3:系统扩展性差分布式:  把系统拆分成多个子系统.优点:  1:把模块拆分,使用接口通信,降低模块之间的耦合度.  2:把项目拆分成若干个子项目,不同的团队负责不同的子项目.
Wesley13 Wesley13
3年前
Java开发工程师最新面试题库系列——Spring部分(附答案)
Spring1.Spring框架是什么?答:Spring是轻量级的面向切面和控制反转的框架。_初代版本为2002年发布的interface21_,Spring框架是为了解决企业级应用开发的复杂性的出现的,它可以帮助开发人员管理对象之间的关系。能实现模块与模块之间、类与类之间的解耦合,Spring是一个大杂烩,它集成其他
Wesley13 Wesley13
3年前
Java 面向对象的设计原则
一、1、面向对象思想的核心:封装、继承、多态。2、面向对象编程的追求:  高内聚低耦合的解决方案;  代码的模块化设计;3、什么是设计模式:  针对反复出现的问题的经典解决方案,是对特定条件下(上下文)问题的设计方案的经验总结,是前人设计实践经验的精华。4、面向对象设计原则
Wesley13 Wesley13
3年前
Java web 前端面试知识点总结
分享一下我的面试知识点总结: 耦合性:也称块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息内聚性:又称块内联系。指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。若一
linbojue linbojue
7个月前
史上最全的后端技术
系统开发1.高内聚/低耦合高内聚指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。模块的内聚反映模块内部联系的紧密程度。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方