面向对象(精髓)变继承关系为组和关系(_Decorator模式)

在这里插入图片描述

在软件开发中,设计模式是解决常见问题的可重用解决方案。在面向对象编程中,继承和组合是两种常用的代码复用方式。然而,随着软件需求的不断变化,我们需要更灵活的设计方式来应对不断变化的需求。在本文中,我们将讨论从继承到组合的演进之路,并探讨如何通过组合设计模式来解决问题。

假如:要实现同时记日志和事务提交如何做比较好

不推荐写法(继承关系)

继承设计的缺陷在于它限制了代码的扩展性和灵活性。具体来说,它导致了以下问题:
在上面的 UML 图中,我们展示了使用继承关系实现日志记录和事务管理的示例。在这个示例中,我们有一个抽象类 LoggingRunnableTransactionalRunnable,它们都包含了一个抽象方法 doRun() 来执行具体的任务。然后,我们有一个具体的类 CodingTask,它继承自 LoggingRunnableTransactionalRunnable,并实现了 doRun() 方法来执行编码任务。

现在,假设我们需要修改系统,让每个编码任务先记录日志,然后再进行事务管理。使用继承关系,我们需要在现有的类结构中进行大量的修改,这可能会导致代码变得复杂和难以维护。下面是一个示例代码,展示了在现有系统中添加事务管理的改动:

public abstract class LoggingTransactionalRunnable extends LoggingRunnable {
    private final Runnable innerRunnable;

    public LoggingTransactionalRunnable(Runnable innerRunnable) {
        this.innerRunnable = innerRunnable;
    }

    @Override
    public void run() {
        super.run();
        beginTransaction();
        innerRunnable.run();
        commitTransaction();
    }

    private void beginTransaction() {
        // 开启事务
    }

    private void commitTransaction() {
        // 提交事务
    }
}

在这个示例中,我们创建了一个新的抽象类 LoggingTransactionalRunnable,它继承自 LoggingRunnable,并实现了事务管理的功能。然后,我们修改了 CodingTask 类,使其继承自 LoggingTransactionalRunnable,以实现先记录日志,然后执行编码任务,最后进行事务管理的功能。

尽管这种方法可以实现需求,但它会导致现有系统的大量修改,可能会破坏现有的代码结构,增加代码的复杂性和难以维护性。因此,使用继承关系来实现这种需求存在 类耦合度高单一继承限制难以复用和测试可能不是最佳选择。

相对于继承关系,组合关系具有更低的耦合度和更高的灵活性。通过组合关系,可以将不同的功能模块化,并在运行时动态地组合它们,从而实现更灵活和可扩展的设计。因此,在设计类和对象时,应尽量避免过度使用继承关系,而是倾向于使用组合关系来实现代码的复用和扩展。

推荐写法(组合关系)

在这里插入图片描述
箭头表示实现关系和组合关系。其中 CodingTask、LoggingRunnable 和 TransactionalRunnable 类都实现了 Runnable 接口,表示它们都是可以在单独的线程中运行的任务。而 LoggingRunnable 和 TransactionalRunnable 类分别包含了一个 Runnable 类型的私有属性,表示它们与内部的任务对象发生了组合关系。
在这里插入图片描述

//LoggingRunnable.java
public class LoggingRunnable implements Runnable {

  private final Runnable runnable;

  public LoggingRunnable(Runnable innerRunnable) {
    this.runnable = innerRunnable;
  }

  @Override
  public void run() {
    long startTime = System.currentTimeMillis();
    System.out.println("Task started at "
        + startTime);
	//Decorator 模式是一种常见的设计模式,用于动态地为对象添加额外的功能,而不需要修改其原始类。
    runnable.run();

    System.out.println("Task finished. Elapsed time: "
        + (System.currentTimeMillis() - startTime));
  }
}


// CodingTask.java
public class CodingTask implements Runnable {

  private final int employeeId;

  public CodingTask(int employeeId) {
    this.employeeId = employeeId;
  }

  @Override
  public void run() {
    System.out.println("Employee " + employeeId
        + " started writing code.");

    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }

    System.out.println("Employee " + employeeId
        + " finished writing code.");
  }
}

上面的代码展示了使用组合关系实现的两个类:LoggingRunnableCodingTask

  1. LoggingRunnable 类:该类实现了 Runnable 接口,并包含了一个私有成员变量 runnable,类型为 Runnable 接口。在构造函数中,通过参数传入一个 Runnable 对象,然后在 run() 方法中,首先记录了任务开始的时间,然后调用传入的 Runnable 对象的 run() 方法执行具体的任务,最后记录了任务结束的时间。

  2. CodingTask 类:该类也实现了 Runnable 接口,表示一个编码任务。在 run() 方法中,它简单地打印出员工开始编写代码的消息,然后休眠 5 秒钟(模拟编写代码的过程),最后打印出员工编写代码完成的消息。

这两个类之间的关系是组合关系,即 LoggingRunnable 类包含一个 Runnable 接口的实例。通过这种组合关系,LoggingRunnable 类可以在执行任务前后添加额外的功能,而不需要修改 CodingTask 类的代码。这符合了开闭原则,即对扩展开放,对修改关闭。

功能扩展

如果有一天项目经理添加新需求事务和MQ 整合

对于组合设计模式,你可以想象一个新的扩展类,例如 MessagingTransactionalRunnable,它可以组合 TransactionalRunnableMessagingRunnable 两个功能,从而实现事务管理和向 MQ 发送消息的组合。下面是一个简单的示例:
在这里插入图片描述

public class MessagingTransactionalRunnable implements Runnable {

  private final Runnable innerRunnable;
  private final String message;

  public MessagingTransactionalRunnable(Runnable innerRunnable, String message) {
    this.innerRunnable = innerRunnable;
    this.message = message;
  }

  @Override
  public void run() {
    try {
      beginTransaction();
      sendMessage(message);
      innerRunnable.run();
      commit();
    } catch (Exception e) {
      rollback();
      throw new RuntimeException(e);
    }
  }

  private void sendMessage(String message) {
    System.out.println("Sending message to MQ: " + message);
    // 实现向 MQ 发送消息的逻辑
  }

  private void commit() {
    System.out.println("Commit transaction");
    // 实现事务提交的逻辑
  }

  private void rollback() {
    System.out.println("Rollback transaction");
    // 实现事务回滚的逻辑
  }

  private void beginTransaction() {
    System.out.println("Begin transaction");
    // 实现事务开始的逻辑
  }
}

在这个示例中,MessagingTransactionalRunnable 类组合了 TransactionalRunnableMessagingRunnable 两个功能模块,通过调用它们的方法来实现事务管理和向 MQ 发送消息的组合。当 MessagingTransactionalRunnable 对象的 run 方法被调用时,它会先开始事务,然后发送消息到 MQ,执行内部的任务,最后提交或回滚事务。这样,你就可以很方便地使用组合设计模式来扩展功能了。

总结

继承的局限性

继承是面向对象编程中的一种重要概念,它允许子类继承父类的属性和方法。通过继承,可以实现代码的重用和扩展。然而,继承也存在一些局限性:

  • 耦合度高: 子类与父类之间存在紧密的耦合关系,子类的实现依赖于父类的具体实现细节。
  • 继承链过长: 当继承层次较深时,维护和理解代码变得困难,容易造成代码膨胀和复杂性增加。
  • 单一继承: 在单继承语言中,子类只能继承一个父类,限制了代码的灵活性和可复用性。

由于这些局限性,我们需要寻找一种更灵活的设计方式来解决问题。

组合的优势

组合是另一种常见的代码复用方式,它允许将对象组合在一起以实现新的功能。相比于继承,组合具有以下优势:

  • 低耦合度: 组合将对象之间的耦合度降低到最低限度,每个对象都可以独立存在并且可以被替换或重用。
  • 灵活性: 通过组合,可以动态地组合和重组对象以实现不同的功能,而不需要修改原始类的代码。
  • 多态性: 组合提倡面向接口编程,利用多态性来实现代码的灵活性和可扩展性。

扩展阅读

在这里插入图片描述

面向对象主题链接
类与对象链接
接口与抽象类链接
不可变性链接
变继承为组合(精髓一)状态模式链接
变继承为组合(精髓二)装饰器模式链接

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手机和电脑同步的好用记事本软件有哪些

我常常需要随手记录各种信息,以便随时查阅和使用。比如,在下班路上,我会用手机记录明天要处理的工作事项、购物清单,或是某个突然迸发的创意想法;而在办公室,我则需要在电脑上整理会议纪要、项目计划&#…

2024年华为HCIA-DATACOM新增题库(H12-811)

801、[单选题]178/832、在系统视图下键入什么命令可以切换到用户视图? A quit B souter C system-view D user-view 试题答案:A 试题解析:在系统视图下键入quit命令退出到用户视图。因此答案选A。 802、[单选题]“网络管理员在三层交换机上创建了V…

(黑马出品_07)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式

(黑马出品_07)SpringCloudRabbitMQDockerRedis搜索分布式 微服务技术分布式搜索 今日目标1.数据聚合1.1.聚合的种类1.2.DSL实现聚合1.2.1.Bucket聚合语法1.2.2.聚合结果排序1.2.3.限定聚合范围1.2.4.Metric聚合语法1.2.5.小…

基于物联网的智能辅助泊车系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 物联网技术 3 1.2 JFinal框架技术 3 1.3 uni-app技术 4 1.4 本章小结 4 2 智能辅助泊车系统的分析 5 2.1 需求分析 5 2. 1.1 系统的功能需求 5 2. 1.2 系统的非功能需求 5 2. 1.3 系统业…

Axios中每次发送post请求前都会发送options请求

今天写前端的时候,发现每次post请求都会失败, 反复调试过后发现axios在每次发送post请求前都发送了options请求, 在网络搜罗了一大圈, 发现了原因是因为web页面发送了请求给vue后, vue再请求后端过程中发生了跨域&…

Covalent(CQT)降低数据可用性成本,加快 Layer2 在 Web3 领域的扩张

Covalent Network(CQT)通过其统一 API,正在为 EVM Layer2 生态系统提供支持,减少了开发者需要导航多个数据供应商的需求,通过解决多链环境中的碎片化挑战,极大地提高了他们的效率。 通过其统一 API 支持 2…

pandas plot函数:数据可视化的快捷通道

一般来说,我们先用pandas分析数据,然后用matplotlib之类的可视化库来显示分析结果。而pandas库中有一个强大的工具--plot函数,可以使数据可视化变得简单而高效。 1. plot 函数简介 plot函数是pandas中用于数据可视化的一个重要工具&#xff0…

Tomcat Web 开发项目构建教程

1下载Tomcat安装包,下载链接:Apache Tomcat - Welcome!,我电脑环境为JDK8,所以下载Tomcat9.0 2、下载完压缩包后,解压到指定位置 3.在intelij中新建一个项目 4.选中创建的项目,双击shift,输入add frame...然…

安卓studio安装(从安装到配置到helloworld)

安卓studio安装 2024.3.11官网的版本(有些翻墙步骤下载东西也解决了) 这次写的略有草率,后面会更新布局的,因为截图量太大了,有需要的小伙伴可以试着接受一下哈哈哈哈 !(https://gitee.com/jiuzheyangbawjf/img/raw/ma…

报名开启丨掘金海外,探寻泛娱乐社交APP出海新风口

随着国内泛娱乐行业用户规模趋于见顶,泛娱乐社交APP转向出海是必然趋势。 根据行业数据显示,有超过35%的国内实时社交企业已启动或者正在规划出海,而其中出海商户的音视频流量增长均超过了100%。尤其是在东南亚、中东、拉美等新兴…

【Linux】文件缓冲区|理解文件系统

目录 预备知识 观察现象 第一:携带\n,不使用fork(),打印到显示器 第二:携带\n,使用fork(),打印到显示器 第三:携带\n,使用fork(),打印到文件里 第四:不携…

Aspose.Words指定位置插入table

如果在创建书签,然后在书签位置插入表格,会出现格式错乱,在单元格位置里面有一个表格,不符合实际使用。正确做法是复制模板文件里面的表格行,然后插入若干行。 如图标记红色位置插入动态数据行,是先复制标…

C# OpenCvSharp 图片批量改名

目录 效果 项目 代码 C# OpenCvSharp 图片批量改名 效果 项目 代码 using NLog; using OpenCvSharp; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows.Forms; namespace OpenCvSharp_Demo { public part…

Pytorch_lightning先前版本的官方文档地址

https://lightning.ai/docs/pytorch/1.4.2/https://lightning.ai/docs/pytorch/1.4.2/ Pytorch_lightning更新到2后,和之前版本的变化较大,一些以前原有的api被删除了 比如:on_pretrain_routine_start 官方也没有在显眼的位置放置先前版本的…

【机器学习智能硬件开发全解】(一)—— 政安晨:从深度认知开始

从深刻认知开始 作者政安晨依据自身多年研发经验,本着成过的功与踩过的坑都是浮云的态度,为各位小伙伴们进行一个系列的智能硬件开发领域的示范解析,让对智能硬件感兴趣的小伙伴们少走弯路。 这是一路走一路全是干货的文章系列,…

基础的渗透测试

信息收集漏洞挖掘获取shell phpadmin文件上传与文件包含 对某一个网站的渗透 专注于web漏洞挖掘、内网渗透、免杀和代码审计,感谢各位师傅的关注!网安之路漫长,与君共勉! 信息收集 简单的进行一下信息收集,goby工具走一…

【工具】Jmeter安装入门

目录 前言 安装Jmeter 1.1 下载Jmeter 1.2 解压 1.3 运行 快速入门 2.1 设置中文语言 2.2 外观(主题)设置 2.3 基本用法 前言 1W:什么是 Jmeter ? Jmeter 是一个用于进行性能测试、负载测试和功能测试的开源工具。最初为 Web 应用程序设计&#…

【已解决】git无法上传大于100MB问题

问题: 输入命令 git config http.postBuffer 524288000 查看命令 git config -l

C语言——指针全讲

1、指针是什么:数据存于内存中,CPU如果要对数据进行处理首先就要找到相对应的数据,就是通过地址来找到的。就像你所在的公寓,你如果要找到你的房间就需要根据门牌号寻找。 在CPU和内存之间有地址线连接,其中在x86环境下…

Socket通信Demo(Unity客户端和C#)

Socket通信基本流程 首先要启动服务器创建Socket,然后要绑定服务器的一个端口这样客户端通过服务器IP端口号就能连接到服务器了服务器接下来会设置监听队列,监听并等待要连接到它的客户端客户端在服务器启动之后也建立自己的Socket,然后使用…