软件设计不是CRUD(5):耦合度的强弱(下)

======== 接上文《软件设计不是CRUD(4):耦合度的强弱(上)》

1.5、数据耦合

在模块间耦合强度已经降低至控制耦合的基础上,如果被调用的模块要求传入的是简单的数值,或者一种抽象的结构。这种依赖强度叫做数据依赖。前文已经说过,数据依赖和内容依赖是有本质区别的,两者只是在称呼上有所接近(不再赘述区别)。

在Java语言(或其他高级语言)中,这种抽象结构可能是某种业务模型的上层接口,也可能是某几种具体业务模型的父级抽象类。例如:动物对象(不是某种具体的动物)、植物对象(不是某种具体的植物)、车辆对象(不是某种具体的车辆)。这就要求被调用的模块内部,能够适应这些抽象对象并保证内部逻辑适应于所有抽象场景,而不是像控制耦合或者标记耦合那样,要求调用者按照被调用的模块内部逻辑做适应。

在这里插入图片描述
以下是标准的数据耦合代码示例:

  • 首先是被调用方(被调用的模块)的示例代码
// 这是一个接口,只要实现了该接口的任何具体动物信息都能传入
// 接口中只规定了必须返回目前的动物类型
public interface Animal {// 传入的动物必须指定一种动物的说明public String getType();
}// 这是专门给调用者使用的调用方式
public interface AnimalService {public void create(Animal animal);
}// 由于动物模块内部实际上也不知道会有哪些动物类型被传入
// 所以关于AnimalService接口的实现,只能按照现有需求中明确的几种动物书写代码
@Service
public class AnimalServiceImpl implements AnimalService {@Overridepublic void create(Animal animal) { // 在进行边界校验后,通过if分支来处理不同动物的不同添加行为// 这些动物的属性和关联信息可能完全不一样if(StringUtils.equals(animal.getType(), "lion")) {// ...... 处理狮子的逻辑} if(StringUtils.equals(animal.getType(), "tiger")) {// ...... 处理老虎的创建逻辑}if(StringUtils.equals(animal.getType(), "seal")) {// ...... 处理海豹的创建逻辑}throw new UnsupportedOperationException("不支持的动物类型");}
}
  • 以下是调用方的调用方式代码
/*** 在调用方中进行具体的长颈鹿这种动物的对象定义。* 注意,这种耦合强度下,如何定义具体的数据结构,话语权在调用方* 长颈鹿显然被调用方是没法处理的,只可能抛出异常* @author yinwenjie*/
public class GiraffeInfo implements Animal {private static final String GIRAFFE = "giraffe";private Integer age;private String fieldA;private String fieldB;// ..... 还有其它和长颈鹿特点有关的字段@Overridepublic String getType() {return GIRAFFE;}// ......其他的get、set省略
}// 然后调用方以如下方式进行调用
// ......
animalService.create(new GiraffeInfo());
// ......

数据耦合的核心在于,上文介绍的更强的耦合度中,调用模块必须适应被调用模块的数据结构,也就是说数据结构的要求由被调用方说了算,调用者需要符合这个规范。而从这个耦合强度开始,被调用者不再规定数据结构的具体要求,只提出一个泛化的结构概念(例如只要传入的动物对象有一个类型,被调用者就认为这是一个合法的动物对象),怎么定义数据结构的话语权在调用者一侧

但是也由于被调用者失去了对数据结构定义的话语权,所以被调用方就需要完整考虑调用方传入数据结构的可能性。这在实际工作中又是不现实的:自然界中涉及的动物何止千种,本应用软件需要管理的动物何止3种,难道每新增一种动物的支持都需要被调用方修改create方法的具体实现?通过以上代码我们就发现了这个情况,如果外部调用者传入的是一个抽象对象,那么模块内部通过“if…if…if…”的处理分支方式是无法穷举所有的场景情况的。上文的演示代码中,调用方最终会收到一个由被调用方抛出的异常“UnsupportedOperationException”,因为被调用的模块中,不支持对“长颈鹿”这种动物的处理 !_^。

另外,细心的读者可能会发现,被调用方(动物模块)做了这么多改动,但是外部调用者目前的调用代码还并没有报错。显然动物模块的内聚性正在逐步形成,设计调整的涟漪效果也开始减少。不过调用者传入的长颈鹿数据仍然会抛出异常,这主要是因为系统调用需求之初就没有考虑到,或者说无法考虑的那么周全:调用者会有这种名叫长颈鹿的动物会需要进行业务支撑。看来实现数据耦合确实有一定难度,那么有没有一种更便于扩展、更降低耦合性的方式呢?

1.6、间接耦合——需达到的设计目标

实际上上文各小节介绍的模块间耦合强度都是可以优化的耦合强度,真正需要设计达到的耦合强度目标是间接耦合。间接耦合是指被调用的模块本身“不知道”如何处理业务逻辑,只是负责“缝合”模块内部处理逻辑和业务场景的关系。是不是不好理解,那么换句话进行表述:模块内部没有处理逻辑,只负责组装处理逻辑,且处理逻辑本身是可以增加。这里我们来看一种利用行为模式达到模块的间接耦合的示例:

注意:由于是基于上一小节中“数据耦合”的代码进行改造,所以有的代码片段就直接省略了。

  • 首先我们改造被调用方(被调用模块)的构造结构
// 首先我们再被调用方的模块内,再增加一个除了service形式的接口以外的业务策略接口
// 这个接口定义了当某个具体的动物需要进行创建时,应该如何进行处理
public interface AnimalStrategy {/*** 在创建操作发生前该方法会触发,外部调用者调用create方法所传入的具体动物对象,会作为该方法的参数被传入* @param animal* @return 如果该策略的实现支持这种动物的处理过程,则返回true;其他情况返回false*/public boolean matched(Animal animal);/*** 如果当前处理策略的matched方法返回true,则该方法会被触发,用于处理这个具体动物数据的添加操作* @param animal*/public void doCreate(Animal animal);
}// ================= 
// 接着,由于在需求之初被调用方(动物模块)就知道了系统中有狮子和老虎两种动物需要支持
// 且这两种动物在使用了本模块的各个系统中都经常被使用,且需求重合度很高
// 于是就直接在被调用方内,为狮子和老虎这两种具体动物做了业务策略的实现(作为模块提供的默认实现),如下所示:// 首先是狮子的实现
@Component
public class AnimalForLionStrategy implements AnimalStrategy {@Overridepublic boolean matched(Animal animal) {return StringUtils.equals(animal.getType(), "lion");}@Overridepublic void doCreate(Animal animal) {Lion lion = (Lion)animal;// ...... // 这里进行具体的狮子这种动物数据的创建过程}
}// ========== 
// 然后是老虎的实现
@Component
public class AnimalForTigerStrategy implements AnimalStrategy {@Overridepublic boolean matched(Animal animal) {return StringUtils.equals(animal.getType(), "tiger");}@Overridepublic void doCreate(Animal animal) {// ...... // 这里进行具体的老虎这种动物数据的创建过程}
}

最后我们修改上文中AnimalService的实现,不在AnimalService的实现类中处理具体的业务过程,而是用它来做已经实现的各个具体业务处理策略的逻辑控制。为了简化,这里的示例代码业务用到一些spring中的常用注解:

// 被调用的“动物模块”,默认的具体实现。其中是一个控制逻辑而不是某种具体业务的处理过程 
@Service
public class AnimalServiceImpl implements AnimalService {@Autowiredprivate List<AnimalStrategy> animalStrategies;@Override@Transactionalpublic void create(Animal animal) {// 进行边界校验后查询可以使用的策略AnimalStrategy currentAnimalStrategy = null;for (AnimalStrategy animalStrategy : animalStrategies) {if(animalStrategy.matched(animal)) {currentAnimalStrategy = animalStrategy;break;}}// 如果找到了对应的处理策略,就进行doCreate方法的调用// 其它情况抛出异常if(currentAnimalStrategy != null) {currentAnimalStrategy.doCreate(animal);return;}throw new UnsupportedOperationException("不支持的动物类型");}
}

有了AnimalStrategy策略接口这样的设计,我们就可以将动物模块的对业务的具体处理过程和对动物模块的调用过程分离,具体来说就是,这种“长颈鹿”动物的业务逻辑处理不需要再动物模块形成之初就进行穷举考虑(前文说过这个要求不现实),当调用者需要自定义这种业务场景时,其业务模型和处理过程都可以由调用方自行扩展,具体代码示例过程如下:

  • 调用方的模块内实现AnimalStrategy接口,实现一个当前系统中特有的“长颈鹿”的处理过程
// 由调用者根据自己特有的业务场景,扩展对“长颈鹿”这种动物的处理过程
// 这个“长颈鹿”的处理业务,其它应用系统中用不到
public class AnimalForGiraffeStrategy implements AnimalStrategy {@Overridepublic boolean matched(Animal animal) {return StringUtils.equals(animal.getType(), "giraffe");}@Overridepublic void doCreate(Animal animal) {// ...... // 首先进行边界校验,// 这里进行具体的长颈鹿这种动物数据的创建过程}
}

以上代码是一种典型的策略模式(行为模式的一种),service被改造成了典型的门面模式。如下图所示:
在这里插入图片描述
在以上的示例中,模块调用层专门的接口实现中,其工作逻辑并不是描述某种具体的业务实现过程,而是描述了一种特定的控制过程,这个控制过程压根不知道具体的业务实现是什么样的,只是按照既定的控制顺序激活某种实现,最后再由具体的某种实现处理对应的业务分支,而且这种业务分支可以无限的扩展下去,而不会影响已有的其它业务分支。

  • 由于所有的业务逻辑都是根据数据模型映射的独立业务处理过程,所以各个业务逻辑间不会受到各自的变化影响;外部调用者的业务逻辑也不会受到被调用模块的影响,因为具体的数据结构如何定义的话语权在被调用模块。这样的设计很好的控制了设计调整的涟漪效果。

  • 控制耦合除了关注业务逻辑的隔离,还关注业务逻辑的扩展性。可以在模块内部已有逻辑和模块外部已有调用不改变的情况下,扩展出新的业务处理过程。这是一个针对“面向新增开发,面向修改关闭”的重要原则落地。这个原则保证了,如果开发人员按照提供的业务接口进行新的业务开发,就不会产生循环依赖。

本专题的目标就是帮助读者在实际工作中,依靠业务抽象的需求分析方式,在应用程序中各模块的设计中运用不同的设计模式来满足复杂的业务需求场景,降低模块和模块间的耦合强度直到达到间接耦合的要求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/162335.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

centos7安装nginx-阿里云服务器

1.背景 2.准备工作步骤 2.1.安装gcc 阿里云服务器一般默认是安装了的 检查是否已安装 gcc -v 出现如下信息表示已安装: 如果没有安装,执行 yum -y install gcc 2.2.安装pcre,pcre-devel yum install -y pcre pcre-devel 2.3.安装zlib yum install -y zlib zlib-devel…

【实践篇】一次Paas化热部署实践分享 | 京东云技术团队

前言 本文是早些年&#xff0c;Paas化刚刚提出不久时&#xff0c;基于部门内第一次Paas化热部署落地经验所写&#xff0c;主要内容是如何构建一些热部署代码以及一些避雷经验。 一、设计-领域模型设计 1.首先&#xff0c;确定领域服务所属的领域 2.其次&#xff0c;确定垂直…

每天一个公众号干货|定时群发教程

每天一个公众号排版小知识&#xff0c;今天分享的是定时群发功能&#xff0c;一次性给你讲清楚 公众号定时群发可以让我们在固定的时间精准的发表文章&#xff0c;这对发文强迫症的小伙伴来时是一个非常神仙的功能&#xff0c;如果群发之前有事耽误发表了&#xff0c;也可以使…

中国社科院大学-新加坡新跃社科大学全球战略领导力博士学位教育项目招生简章

Singapore University of Social Sciences--University of Chinese Academy of Social Sciences Doctoral program on Global Strategic Leadership V13146152701 一、项目简介 全球经济正在经历由科技进步和创新、政治和人口剧烈变化所带来的巨大的不确定性和挑战。面对日…

Docker学习——③

文章目录 1、Docker Registry&#xff08;镜像仓库&#xff09;1.1 什么是 Docker Registry&#xff1f;1.2 镜像仓库分类1.3 镜像仓库工作机制1.4 常用的镜像仓库 2、镜像仓库命令3、镜像命令[部分]4、容器命令[部分]4.1 docker run4.2 docker ps 5、CentOS 搭建一个 nginx 服…

体验SOLIDWORKS旋转反侧切除增强 硕迪科技

大家在设计中经常使用的旋转切除命令在solidworks2024版本中迎来了新的增强&#xff0c;添加了旋转反侧切除选项。在设计过程中不必修改复杂的草图即可切除掉我们不需要的部分。使设计工作更加方便快捷。 打开零部件后&#xff0c;点击键盘上的S键并输入旋转切除以搜索该命令&a…

【mmcv报错】ModuleNotFoundError: No module named ‘mmcv.runner

跑一个代码需要用到mmcv和mmseg 其中有两行代码&#xff1a; from mmcv.runner import load_checkpoint from mmseg.utils import get_root_logger我先是按照官方推荐的安装方法去安装了mmcv和mmseg pip install -U openmim mim install mmcv它会自动帮你安装mmengine 我的cu…

C++学习之map和set

目录 一&#xff0c;什么是map和set 二&#xff0c;set的使用 插入 键值对 删除&#xff08;erase&#xff09;与查找 lowerbound与upperbound equal_range multiset 三&#xff0c;map的使用 insert 查找 删除 重载[ ] ​编辑 一&#xff0c;什么是map和set C中…

Spring Security入门教程,springboot整合Spring Security

Spring Security是Spring官方推荐的认证、授权框架&#xff0c;功能相比Apache Shiro功能更丰富也更强大&#xff0c;但是使用起来更麻烦。 如果使用过Apache Shiro&#xff0c;学习Spring Security会比较简单一点&#xff0c;两种框架有很多相似的地方。 目录 一、准备工作 …

有没有好用的人事管理系统?什么才是好的人事系统?

人事管理关系到企业各部门的正常运转&#xff0c;一个好的人事管理系统对于公司来说无疑是锦上添花&#xff0c;实现人事个性化管理&#xff0c;可以极大地提高人员管理的效率。但在信息化转型的浪潮下&#xff0c;很多企业人事信息化却遇到不少问题。 那么请花5分钟认真看这篇…

力扣 141.环形链表和142.环形链表2

目录 1.环形链表Ⅰ解题思路2.环形链表Ⅰ代码实现3.环形链表Ⅱ解题思路4.环形链表Ⅱ代码实现 1.环形链表Ⅰ解题思路 利用快慢指针&#xff0c;快指针一次走两个&#xff0c;慢指针一次走一个&#xff0c;如果出现了快指针为空或者快指针的next为空的现象则说明不带环&#xff0…

海康Visionmaster-全局脚本:方案加载完成信号发给通 信设备的方法

需要在方案加载完成后&#xff0c;发送加载完成信号到全局变量&#xff0c;发送给通信设备。 全局脚本的使用可以通过打开示例&#xff0c;完成常用的基本功能开发。 打开全局通信代码后&#xff0c;在脚本中添加代码