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

news2024/11/28 6:31:09

======== 接上文《软件设计不是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 {
  @Override
  public 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;
  // ..... 还有其它和长颈鹿特点有关的字段
  
  @Override
  public 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 {
  @Override
  public boolean matched(Animal animal) {
    return StringUtils.equals(animal.getType(), "lion");
  }
  @Override
  public void doCreate(Animal animal) {
    Lion lion = (Lion)animal;
    // ...... 
    // 这里进行具体的狮子这种动物数据的创建过程
  }
}

// ========== 
// 然后是老虎的实现
@Component
public class AnimalForTigerStrategy implements AnimalStrategy {
  @Override
  public boolean matched(Animal animal) {
    return StringUtils.equals(animal.getType(), "tiger");
  }
  @Override
  public void doCreate(Animal animal) {
    // ...... 
    // 这里进行具体的老虎这种动物数据的创建过程
  }
}

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

// 被调用的“动物模块”,默认的具体实现。其中是一个控制逻辑而不是某种具体业务的处理过程 
@Service
public class AnimalServiceImpl implements AnimalService {
  @Autowired
  private List<AnimalStrategy> animalStrategies;
  @Override
  @Transactional
  public 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 {
  @Override
  public boolean matched(Animal animal) {
    return StringUtils.equals(animal.getType(), "giraffe");
  }
  @Override
  public void doCreate(Animal animal) {
    // ...... 
    // 首先进行边界校验,
    // 这里进行具体的长颈鹿这种动物数据的创建过程
  }
}

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iPortal如何灵活设置用户名及密码的安全规则

作者&#xff1a;yx 目录 前言 一、配置文件介绍 1、<passwordRules>节点 注意事项&#xff1a; 2、<usernameRules>节点 二、应用实例 1、配置文件设置 2、验证扩展结果 三、结果展示 前言 SuperMap iPortal提供了扩展账户信息合规度校验规则的能力&#…

大数据毕业设计选题推荐-设备环境监测平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

V8引擎如何存储对象(VIP课程)

在V8中对象的结构 主要分为三个指针构成的&#xff0c;分别是隐藏类&#xff0c;properties(常规属性)&#xff0c;elements&#xff08;排序属性&#xff09; 了解 常规属性 和 排序属性 let xm {100: "test-100",3: "test-3",C: "test-C",1…

01-基于IDEA,Spring官网,阿里云官网,手动四种方式创建SpringBoot工程

快速上手SpringBoot SpringBoot技术由Pivotal团队研发制作&#xff0c;功能的话简单概括就是加速Spring程序初始搭建过程和Spring程序的开发过程的开发 最基本的Spring程序至少有一个配置文件或配置类用来描述Spring的配置信息现在企业级开发使用Spring大部分情况下是做web开…

MySQL数据库的备份和恢复

备份 完全备份和完全备份 完全备份 完全备份&#xff1a;就是将整个数据库完整的进行备份 增量备份 增量备份&#xff1a;就是在完全备份的基础之上&#xff0c;对后续新增的内容进行备份 备份的需求 1、 在生产环境中数据的安全至关重要&#xff0c;任何数据的丢失都可…

APP测试的7大注意点。

1. 运行 1&#xff09; App安装完成后的试运行&#xff0c;可正常打开软件。 2&#xff09; App打开测试&#xff0c;是否有加载状态进度提示。 3&#xff09; App⻚面间的切换是否流畅&#xff0c;逻辑是否正确。 4&#xff09; 注册 同表单编辑⻚面 用户名密码⻓度 …

windows环境下安装Java过程(免登录Oracle官网下载java)

下载路径 oracle官网&#xff1a; java下载路径 Oracle共享账号可下载JDK&#xff1a; 指路 安装流程 执行下载后的jdk的可执行文件一路next下去&#xff0c; 可以自定义安装路径添加环境变量&#xff0c; 两个地方需要添加 在cmd中输入java -version 进行验证&#xff0c;…

【Web】在前端中CSS的语法

CSS规则是由两个主要的部分构成&#xff1a;选择器、以及一条或多条声明。 选择器通常是需要改变的HTML元素。 每条声明由一个属性和一个值组成。 属性&#xff08;Property&#xff09;是需要设置的样式属性&#xff08;Style attribute&#xff09;。每一个属性有一个值。…

vue 实现在线预览Excel-LuckyExcel/LuckySheet实现方案

一、准备工作 1. npm安装 luckyexcel npm i -D luckyexcel 2.引入luckysheet 注意&#xff1a;引入luckysheet&#xff0c;只能通过CDN或者直接引入静态资源的形式&#xff0c;不能npm install。 个人建议直接下载资源引入。我给你们提供一个下载资源的地址&#xff1a; …

vue3实践

选项式API和组合式API 1、使用选项式 API&#xff0c;用包含多个选项的对象来描述组件的逻辑&#xff0c;例如 data、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上&#xff0c;它会指向当前的组件实例。 2、组合式 API&#xff0c;我们可以使用导入的 A…

4、Python基本数据类型:数字、字符串、列表、元组、集合、字典

文章目录 1、Python基本数据类型简介2、数字3、字符串4、列表5、元组6、集合7、字典1、Python基本数据类型简介 Python是一种非常强大且易于学习的编程语言,它具有简洁的语法和丰富的数据类型。了解和掌握Python的基本数据类型是学习和使用Python的基础。本文将详细介绍Pytho…

Qt 继承QAbstractListModel实现自定义ListModel

1.简介 QAbstractListModel类提供了一个抽象模型&#xff0c;可以将其子类化以创建一维列表模型。 QAbstractListModel为将其数据表示为简单的非层次项目序列的模型提供了一个标准接口。它不直接使用&#xff0c;但必须进行子类化。 由于该模型提供了比QAbstractItemModel更…

今天放个大招,带你手把手搭建 Jenkins 的分布式构建

UI 自动化测试代码写完了以后&#xff0c;会放到 Jenkins 这样的持续集成工具上去构建。 如果 Jenkins 平台是搭建在服务器上&#xff0c;会面临 2 个问题&#xff1a; 第一个问题是 UI 自动化测试需要渲染界面&#xff0c;需要消耗大量的 CPU 和内存资源&#xff0c;如果服务…

【3D图像分割】基于Pytorch的VNet 3D 图像分割5(改写数据流篇)

在这篇文章&#xff1a;【3D 图像分割】基于 Pytorch 的 VNet 3D 图像分割2&#xff08;基础数据流篇&#xff09; 的最后&#xff0c;我们提到了&#xff1a; 在采用vent模型进行3d数据的分割训练任务中&#xff0c;输入大小是16*96*96&#xff0c;这个的裁剪是放到Dataset类…

家用好物风云汇,值得买风尚购物清单

双十一又要来啦&#xff01;作为购物狂热者的我们&#xff0c;肯定早已经在各大电商平台上准备好了购物清单。但是&#xff0c;如果你还没有找到心仪的好物&#xff0c;那么不妨来看看值得买在线上线下的选品活动。 最近&#xff0c;我在风尚场发现了很多值得买的家用好物&…

js获取url截取文件名或后缀名

示例图 var url "http://localhost:5613/static/挽风.jpg"var lastOf url.lastIndexOf(/) // /所在的最后位置var str url.substr(lastOf 1) //截取文件名称和后缀 输出&#xff1a;挽风.jpgvar strUrl url.substr(0, lastOf) //截取路径字符串 输出&…

【构建一套Spring Cloud项目的大概步骤】【Springcloud Alibaba微服务分布式架构学习资料】

目录 1、创建一个Maven项目2、搭建Spring Cloud服务3、搭建Spring Cloud Eureka4、搭建Spring Cloud Config5、搭建Spring Cloud Consumer6、搭建Spring Cloud Zuul7、使用Jenkins进行代码自动化部署另附录、Springcloud Alibaba微服务分布式架构 1、创建一个Maven项目 在IDEA…

四.pyqt5 登录界面和功能

一.使用qt creator 设置登录界面 主界面为之前设计的界面 from123.py 文章地址&#xff1a;三.listview或tableviw显示 二.导出ui文件为py文件 # from123.py 为导出 py文件 form.ui 为 qt creator创造的 ui 文件 pyuic5 -o x:\xxx\Fromlogin20230809.py form.ui三.python 显…

排查CPU飙高与系统反应慢的问题

今天我要和大家分享的是如何排查系统中的CPU飙高和系统反应慢的问题。在日常的系统运维中&#xff0c;我们可能会遇到这样的问题&#xff0c;因此&#xff0c;我将尽可能详细地介绍排查的步骤&#xff0c;并通过实际的例子来展示如何进行排查。希望大家能够从这篇文章中获得所需…

分享一下微信小程序里怎么创建会员卡功能

在当今的数字化时代&#xff0c;微信小程序已经成为一种广泛使用的应用模式&#xff0c;涵盖了各种行业。对于企业而言&#xff0c;拥有一个会员卡系统可以更好地管理客户&#xff0c;提高客户忠诚度&#xff0c;并促进消费。本文将探讨如何在微信小程序中创建会员卡功能&#…