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

在这里插入图片描述

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

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

不推荐写法(继承关系)

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

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

public abstract class LoggingTransactionalRunnable extends LoggingRunnable {private final Runnable innerRunnable;public LoggingTransactionalRunnable(Runnable innerRunnable) {this.innerRunnable = innerRunnable;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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,执行内部的任务,最后提交或回滚事务。这样,你就可以很方便地使用组合设计模式来扩展功能了。

总结

继承的局限性

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

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

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

组合的优势

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

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

扩展阅读

在这里插入图片描述

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

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

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

相关文章

计算机网络——TCP/IP网络层次模型

计算机网络——TCP/IP网络层次模型 TCP/IP网络模型的起源TCP/IP网络层次的结构TCP/IP如何交互 TCP/IP协议栈TCP/IP协议栈主要协议 TCP/IP 和 OSI之间的区别面向连接和无连接面向连接三次握手,四次挥手 无连接 我们上一次了解了OSI的网络层次模型,如果还没…

Linux学习-内存管理

目录 内存管理 malloc free 使用 字符串存储申请堆区 自主输入个数,然后通过malloc在程序中申请空间,不用必须提前指定大小 内存溢出 内存泄漏 内存碎片 内存管理 函数名就是指向该函数的函数指针。 堆区是自低向高,栈区是自高向低…

ffmpeg日记4001-原理介绍-视频切割原理

原理 打开输入---->打开输出---->根据输入来创建流---->拷贝流设置---->循环读帧---->判断时间点是否到达切割点,并做设置---->设置pts和dts---->写入---->善后 重点是pts和dts如何设置。参考《ffmpeg学习日记25-pts,dts概念的…

ftp速度太慢只有几十k,怎么解决?

FTP是目前许多企业日常运营中还在用的文件传输方式。虽然比较普遍,然而,许多用户在使用FTP时经常遇到速度缓慢的问题,有时甚至只有几十KB/s。这不仅影响工作效率,还可能导致许多数据传输的延迟的问题。本文将探讨FTP速度慢的原因&…

计算机服务器中了faust勒索病毒怎么解密,faust勒索病毒解密工具流程

在互联网飞速发展的今天,越来越多的企业走向了数字化办公模式,许多企业开始利用网络计算机开展各项工作业务,网络也为企业的生产效率提供了极大便利,但网络中存在许多恶意威胁。近日,云天数据恢复中心接到许多企业的求…

Java 学习和实践笔记(34):对象的转型(casting)

对象的转型(casting)有两种,一种是向上转型,一种是向下转型。 向上转型:父类引用指向子类对象。这属于自动类型转换,编译器会自动完成。 上一节的多态中,形参为父类Animal, 但是调用时实参为子类对象Dog&…

linux中查看并修改日期

1.如何在终端控制行界面显示并且调整日期: 显示当前日期: 2.显示当前年份: 3.显示当前月份: 4.显示当前天数: 5.显示到目前为止的天数: 6.显示日期: date “%x“ ≈ date ”%Y %m %d"…

09-设计模式 面试题

你之前项目中用过设计模式吗? 工厂方法模式分类 简单工厂模式工厂方法模式抽象工厂模式工厂模式 需求:设计一个咖啡店点餐系统。 设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore)…

一、什么是 HarmonyOS ?

HarmonyOS 是华为开发的一款面向未来的全场景分布式智慧操作系统,将逐步覆盖 18N 全场景终端设备。 对消费者而言,HarmonyOS 用一个“统一的软件系统”,从根本上解决消费者面对大量智能终端体验割裂的问题,为消费者带来统一、便利…

Kafka Stream入门

1. 什么是流式计算 流式计算(Stream Processing)是一种计算模型,旨在处理连续的数据流。与传统的批处理模型不同,流式计算可以实时或接近实时地处理和分析数据,这意味着数据在生成后不久就被处理,而不是存…

基于android的物业管理系统的设计与实现19.8

目录 基于android的物业管理系统的设计与实现 3 摘 要 3 Android property managemengt system 5 Abstract 5 1 绪论 6 1.1 选题背景 6 1.2 课题研究现状 6 1.3 设计研究主要内容 7 1.4 系统主要设计思想 8 2 开发环境 8 2.1 Android系统的结构 8 图2-1 Android系统架构图 9 2…

Python绘图-14绘制3D图(下)

14.7绘制3D等高线图个性化colormap 14.7.1图像呈现 14.7.2绘图代码 import numpy as np # 导入numpy库,numpy是Python的一个强大的数值计算扩展程序库,支持大量的维度数组与矩阵运算。 import matplotlib.pyplot as plt # 导入matplotlib的绘图模块p…

UDP编程及特点

目录 1.UDP编程流程 2.recvfrom()、sento() 3.代码演示 3.udp特点 1.UDP编程流程 socket()用来创建套接字,使用 udp 协议时,选择数据报服务 SOCK_DGRAM。sendto()用来发送数据,由于 UDP 是无连接的,每次发送数据都需要指定对端…

神经网络基本使用

1. 卷积层 convolution layers import torch import torchvision from torch import nn from torch.nn import Conv2d from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriterdataset torchvision.datasets.CIFAR10(./dataset,trainFa…

Aspose.Words指定位置插入table

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

day1_C++:实现C++风格字符串输出

1.提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数&#xff0c;要求使用C风格字符串完成 程序代码&#xff1a; #include <iostream>//标准输入输出流 #include <string.h>//C中字符串相关头文件 using na…

HBase分布式数据库的原理和架构

一、HBase简介 HBase是是一个高性能、高可靠性、面向列的分布式数据库&#xff0c;它是为了在廉价的硬件集群上存储大规模数据而设计的。HBase利用Hadoop HDFS作为其文件存储系统&#xff0c;且Hbase是基于Zookeeper的。 二、HBase架构 *图片引用 Hbase采用Master/Slave架构…

LeetCode226题:翻转二叉树(python3)

class Solution:def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:if not root:return rootleft self.invertTree(root.left)right self.invertTree(root.right)root.left,root.right right,leftreturn root复杂度分析 时间复杂度&#xff1a;O(N…

HTML 学习笔记(十一)表单

一、分块 1.单行文本框控件–文本框和密码框 文本框控件通过单标签input实现&#xff0c;其具有必要属性type来控制输入控件的类型(默认为text即文本信息)&#xff0c;密码框的type为password(口令)。   表单的动作属性定义了目的文件的文件名。由动作属性定义的这个文件通常…

Docker 安装部署MySQL教程

前言 Docker安装MySQL镜像以及启动容器&#xff0c;大致都是三步&#xff1a;查询镜像–>拉取镜像–>启动容器 1、查询镜像 docker search mysql2、拉取镜像 拉取镜像时选择stars值较高的 docker pull mysql:5.7 #这里指定拉取对应的版本Mysql5.7&#xff0c;没有指…