内聚和耦合的例子(内聚和耦合的区别)

计算机(手机)的构造是什么?

键盘鼠标(输入)+ 内存CPU(计算)= 屏幕显示声音输出(输出)

简单吧?

输入+计算=输出 的抽象,本文简称为三要素。它远不止用在计算机的体系构造上。

实际上,它抽象了计算机(硬件)和互联网(软件)世界里的一切。

内聚和耦合的例子(内聚和耦合的区别)

越理解这个抽象,就越能:

  • 抓住编写代码和系统架构的重点
  • 掌握正确的学习方向
  • 事半功倍,不会越学越迷茫

这就是本文的目标。

方法设计

首先,让我们从编程设计的最小粒度说起:方法(也叫函数)。

如下(接口中方法定义):

public Document queryById(String docId);

public boolean updateTitle(String docId, String title);

抛开 public 关键字不谈,不是重点,仅为语言的周边细节。

第一个方法(根据文章ID查询文章对象),拆解如下:

  1. Document 是方法的返回值,定义了输出
  2. queryById 是方法名,方法名称和方法体,就是计算
  3. String docId 是方法参数,定义了输入

所以这个方法的含义也可以这么定义:

输入(String docId)+ 计算(queryById)= 输出(Doc)

第二个方法(根据文章ID更新它的标题),拆解类似。

发现了规律没有?

事实上所有的方法定义,都只表述了同一种抽象,那就是输入、计算、输出。

这三个要素唯一重要,三个中的每一个的定义都应该力求简单直观,没有二义性,突出逻辑重点。

好,我们再看一个例子。大家先看方法定义:

public void update(Document doc, Map<String, String> segmentCategory, int minTitleLen, Map<String, Long> categoryExpire);

能看出来这个方法是做什么的吗?

这个方法的逻辑是:

  • 对文章做分词
  • 然后根据segmentCategory这个分词到分类的映射,取到文章的分类
  • 然后根据categoryExpire分类的过期时间算出文章的过期时间
  • 最终更新文章的过期时间

回过头来看下这个方法定义,你觉得这个方法的定义做的好还是不好?

判断方法定义好坏的一个策略就是拆解三要素:

  • 输入:不清晰。参数众多,参数的目标不清晰,比如根据Document的什么属性来分词,minTitleLen这个参数是干嘛的。这么多参数到底是做什么的?
  • 计算:update。这个方法名不知所云,完全不知道是要更新什么字段
  • 返回值:void。也就是没有返回值,从返回值角度也看不出这个方法核心要做什么逻辑

所以,改造方案如下:

public String getCategory(String title, int minTitleLen, String content, Map<String, String> segmentCategory);

public Long getExpire(Date docPublishDate, String category, Map<String, Long> categoryExpire);

之前的方法被重构为两个方法:

输入(满足最小标题长度的标题、正文、分词到分类的映射)+ 计算(计算出文字的所属分类)= 输出(分类)

输入(文章发布时间、分类、分类到过期间隔的映射)+ 计算(计算出文章过期时间)= 输出(过期时间)

可以看到重构之后,三要素简单清晰,两个方法高内聚低耦合。

对于当前短期的业务逻辑开发,不容易出错,对于长期自己或别的维护的人,也容易理解。

其实,无论复杂或简单的业务逻辑,高并发还是低负载的程序系统,核心抽象完全一样。

小到一个函数,大到一个单机程序。

单机程序

有人会问:

  • 方法确实是由输入、计算和输出三要素组成的,因为方法定义就那么简单一行。
  • 可是对于一个独立运行的服务程序,有很多模块包、依赖包、各种类,怎么可能用简单的输入、计算和输出来衡量呢?复杂得多呢?

如下图所示:

内聚和耦合的例子(内聚和耦合的区别)

一个程序(服务),归根结底是由方法组成的。

  • 类是为了将相似操作的方法,归类到一起
  • 包是为了将相似业务的类,归类到一起
  • 单机程序是将一组业务(包),归类到一起

类与类之间,是由多组方法,也就是多组输入计算输出组成的;包与包之间,也一样。

只是越往上层走,输入、输出的维度越多,包含越丰富。

甚至广义上看,不同类、不同包,也是可以基于输入、计算和输出来作为划分原则的。

以一个标准的spring服务程序来举例:

  • controller层:输入(用户请求参数)+ 计算(service)= 输出(用户响应)
  • service层:输入(controller层部分参数)+ 计算(本地逻辑)= 输出(部分结果)
  • dao层:输入(service层部分参数)+ 计算(存储交互)= 输出(存储交互结果)

小结:
狭义上看,单机程序是由很多组方法有机构成的,定义好每一个方法的输入、计算和输出,非常重要;
广义上看,类或包(模块)之间,也是基于数据流的输入、计算和输出的抽象,理清或划分好输入、计算和输出这三要素,就能做好一个单机程序的架构设计。

分布式服务

是的。

输入、计算和输出,还能抽象更加复杂的分布式服务集群的架构。

从这个角度理解,实际上,分布式服务抽象成输入、计算和输出,比单机程序更加显而易见。

如下图:

内聚和耦合的例子(内聚和耦合的区别)

上面举例画的一个分布式服务的组成架构图,体现了由不同组服务集群以及不同组存储集群的组成和核心的交互。

之所以说分布式服务,更加容易看出是输入、计算和输出三要素的抽象,是因为图中的箭头刚好表示了计算的方向。

  • 箭头的起点方向的服务是整体架构中的输入
  • 箭头的终点方向的服务是整体架构中的计算
  • 从计算服务出发的,箭头的终点方向的存储或服务,是输出

服务与服务之间的交互、服务与存储之间的交互、存储与存储之间的交互,都可以用输入、计算和输出三要素来解释抽象。

分布式服务架构的设计,本质上也就是理清和划分好这三要素。

一个架构图设计出来,如何分辨好坏,就是看每一个服务集群、每一组存储集群,他的输入、计算和输出是否定义的明确、唯一、简单。

总结

方法逻辑即方法体可以做到非常复杂,单机程序也可以实现众多复杂的业务逻辑,分布式服务除了实现复杂的业务逻辑外,本身的技术交互也很复杂。

但其本质都非常简单,且无论多么复杂、粒度粗细,都可以有一个统一的抽象:输入、计算和输出。

站在这个角度读懂或者自己设计,方法、单机程序和分布式服务,都将变得简洁明了,且可以看透本质。

主要是因为复杂的本质都是在计算里,而三要素中的计算部分,实际上忽略了它的内在明细。

因为只要定义好了输入和输出,怎么计算其实不重要。

本文完。

本文是编码人生和程序本质系列的第1篇。下一篇:编程让程序或集群运行起来。

领取300本育儿电子书,30门名师育儿大课,添加 微信:egm229  备注:Y

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 1065899103@qq.com 举报,一经查实,本站将立刻删除。
如若转载,请注明出处:https://www.cxyyzs.cn/6767.html