如何动态的给一个类增加功能?

news2025/8/4 19:34:06

装饰器模式概述

装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为,在现实生活中,这种情况也到处存在,例如一张照片,我们可以不改变照片本身,给它增加一个相框,使得它具有防潮的功能,而且用户可以根据需要给它增加不同类型的相框,甚至可以在一个小相框的外面再套一个大相框。

装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。

装饰模式结构图:

  • Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

  • ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。

  • Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。

  • ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

简单案例

场景:天气太热了,喝点儿冰水解解暑;加点儿柠檬片,让果汁好喝点儿。

先定义一个喝水的接口

public interface Drink {
    /**
     * 喝水
     */
    void drink();
}
复制代码

写一个接口的实现

public class DrinkWater implements Drink {

    @Override
    public void drink() {
        System.out.println("喝水");
    }

}
复制代码

一个简单的装饰器

public class DrinkDecorator implements Drink {

    private final Drink drink;

    public DrinkDecorator(Drink drink) {
        this.drink = drink;
    }

    @Override
    public void drink() {
        System.out.println("先加点儿柠檬片");
        drink.drink();
    }

}
复制代码

测试

public class DrinkMain {
    public static void main(String[] args) {
        Drink drink = new DrinkWater();
        drink = new DrinkDecorator(drink);
        drink.drink();
    }
}
复制代码

运行结果

先加点儿柠檬片
喝水
复制代码

一个简单的装饰器模式例子就写完了,这里只是先了解一下装饰器模式。

案例场景

大家应该都喝过奶茶吧,我经常喝的原味奶茶,里面可以添加配料,每增加一种配料,该奶茶的价格就会增加,要求计算一种奶茶的价格。

下面就可以使用装饰器模式来模拟这种场景。

定义抽象构件类

/**
 * 奶茶
 */
public interface MilkyTea {
    double cost();
}
复制代码

定义具体构件类

/**
 * 珍珠奶茶
 */
public class PearlMilkyTea implements MilkyTea{
    @Override
    public double cost() {
        return 13.0;
    }
}
复制代码

定义抽象装饰类

public abstract class MilkyTeaDecorator implements MilkyTea{

    private MilkyTea milkyTea;

    public MilkyTeaDecorator(MilkyTea milkyTea){
        this.milkyTea = milkyTea;
    }

    @Override
    public double cost() {
        return milkyTea.cost();
    }
}
复制代码

定义具体装饰类

/**
 * 椰果奶茶
 */
public class CoconutDecorator extends MilkyTeaDecorator{

    public CoconutDecorator(MilkyTea milkyTea) {
        super(milkyTea);
    }

    @Override
    public double cost() {
        System.out.println("添加椰果");
        return super.cost() + 2.0;
    }
}


/**
 * 布丁奶茶
 */
public class PuddingDecorator extends MilkyTeaDecorator{

    public PuddingDecorator(MilkyTea milkyTea) {
        super(milkyTea);
    }

    @Override
    public double cost() {
        System.out.println("添加布丁");
        return super.cost() + 1.5;
    }
}
复制代码

测试代码

public class Client {
    public static void main(String[] args) {
        MilkyTea milkyTea = new PearlMilkyTea();
        milkyTea = new CoconutDecorator(milkyTea);
        milkyTea = new PuddingDecorator(milkyTea);
        System.out.println(milkyTea.cost());
    }
}
复制代码

运行结果

添加布丁
添加椰果
16.5
复制代码

在客户端代码中,我们先定义了一个MilkyTea类型的具体构件对象milkyTea,然后将milkyTea作为构造函数的参数注入到具体装饰类CoconutDecorator中,得到一个装饰之后对象milkyTea,再将装饰了一次之后的milkyTea对象注入另一个装饰类PuddingDecorator中实现第二次装饰,得到一个经过两次装饰的对象milkyTea,再调用milkyTea的cost()方法即可得到一杯既加了布丁又加了椰果的珍珠奶茶价格。

装饰器模式优缺点

优点

  1. 装饰类和被装饰类可以独立发展,不会相互耦合。
  2. 相比于继承,更加的轻便、灵活。
  3. 可以动态扩展一个实现类的功能,不必修改原本代码

缺点

  1. 会产生很多的装饰类,增加了系统的复杂性。
  2. 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

装饰器模式适用场景

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  2. 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类已定义为不能被继承(如Java语言中的final类)。

总结

装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化,以便增加新的具体构件类和具体装饰类。在软件开发中,装饰模式应用较为广泛,例如在JavaIO中的输入流和输出流的设计、javax.swing包中一些图形界面构件功能的增强等地方都运用了装饰模式。

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

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

相关文章

跨域问题?别担心!Spring Boot的5种奇巧淫技解救你

来源:https://blog.csdn.net/weter_drop/article/details/112135940 一、为什么会出现跨域问题 出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同…

SpringBoot自定义注解+异步+观察者模式实现业务日志异步入库

SpringBoot自定义注解异步观察者模式实现业务日志异步入库前言基础环境导入依赖编写yml配置数据库设计代码实现实体类编写注解业务类型枚举编写切片ip工具类事件发布监听者Controller控制层servicemapper验证前言 我们在企业级的开发中,必不可少的是对日志的记录&a…

VSCode将markdown文件导出为带书签的PDF文件

背景 之前找工作的总结性文章,全是markdown形式的想直接变成PDF好携带。方便查找 初步探索遇到的问题 markdown直接在chrome钟打开,右键有打印选项,有一说一,效果不错,唯一缺点,没书签。 怎么办 查资料…

收音机知识,调谐(选频/滤波),调制(升频)

参考:https://www.bilibili.com/video/BV1d14y1N7nm/?spm_id_from333.999.0.0&vd_source00bd76f9d6dc090461cddd9f0deb2d51 有关知识提纲整个信号的传输变化调谐人耳听到声音的频率范围(20~20000Hz)天线和传送信号的波长关系波长和天线长…

LeetCode 剑指 Offer II 106. 二分图【二分图匹配】中等

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…

京东pop店铺订单导出

下载安装与运行 下载、安装与运行 语雀 特别提醒 只能导出已登录店铺的订单导出的收件人手机号是虚拟号 功能 主要是方便线下工厂发货的店主 所见即所得的导出自由选择导出项自由排序Excel导出列顺序导出过程中有进度提示,用户可以随时提前中止 什么是所见即所…

Linux网络编程概述

文章目录 前言一、客户端与服务端二、客户端程序编写三、程序的编译和运行总结前言 本篇文章将带大家来正式学习Linux网络编程。 一、客户端与服务端 一般认为服务器是一个长时间运行的程序(既守护程序)他只在相应来自网络请求时才发送网络消息 协议的另一端是客户端程序,…

Swagger文档注释

本文以DRF框架为例使用 为什么要接口文档注释 一. 方便后端调试与后续接口更新; 二. 对于大型前后端分离项目,前后端人员是分开开发的,甚至前端的人你都不知道远在何处,这时候接口文档的重要性就太重要了。 三. 接口注释文档常用…

巧用千寻位置GNSS软件| 点放样操作指南

在工程测量中,点放样就是将设计或图纸上的点位在实地上测量出来,将目标坐标输入软件在实地放样出来的过程。本文将围绕“如何在千寻位置GNSS软件上完成点放样操作”进行分步骤讲解。点击【测量】->【点放样】->【坐标点库】,选择一个点…

气传导和骨传导耳机的区别?这两种耳机哪个更好更实用?

气传导和骨传导耳机的最大区别:就在于发声原理的不同。 这两种耳机,一种是空气传播声音,一种是骨骼固体传声。 气传导耳机,就是声音从外部传过来,并由外耳、中耳传导到内耳,这是我们日常中接触最多的传导…

第三方支付接口测试面试要点

第三方支付接口测试面试要点 第三方支付接口测试,主要是看你的接口功能实现的是否满足需求,以及你的测试思路是否正确。因此,接口测试工程师要从以下几个方面来准备。 首先,我们需要了解第三方支付的流程; 最后,我们需…

Spring 之 AOP 原理详解

Spring 是一个流行的 Java 企业应用程序开发框架。其中的 AOP(面向切面编程)是 Spring 框架中的一个核心概念。本文将介绍 Spring AOP 的底层实现原理,并通过源代码解析来详细阐述其实现过程。 什么是AOP? AOP是一种编程范式&…

three.js之自定义一个正方体(网格)

本节主要通过自定义顶点和平面的方式,创造一个立方体。真正的开始走近three.js。 效果图 坐标系 坐标系支持右手定则。图中红色是x轴,绿色是y轴,蓝色是z轴 源码 引入的插件js【本人的csdn也有下载资源,如果打不开git可以在csd…

AI网站汇总(免费chatgpt)(60个持续增加中)

本文总结了6大类AI工具,包括:聊天AI、绘画AI、AI提示词、图像处理、UI设计和3D设计,汇总60个AI网站,一键收藏。 目录 一、聊天AI 二、绘画AI 三、AI提示词 四、图像处理

SQL笔记(2)——MySQL的表操作与索引(收藏吃灰版)

本文详细记录如何通过命令的方式修改MySQL的表结构,例如新增列、删除列等;不止学会了,你还学懂了,收藏吃灰~ 开始之前 上一篇文章创建了一些表,ER图如下。本文针对score表进行操作,场景就是新增一个备注rem…

自动化测试面试一周拿到3个offer,只因为我记下了这个文档

目录 一、接口测试基础 二、 接口测试工具 三、自动化测试 四、自动化测试工具 五、总结 一、接口测试基础 1、公司接口测试流程是什么? 从开发那边获取接口设计文档、分析接口并进行用例设计、并提前录入到接口测试工具 jmeter,等开发那边进行…

客户关系管理小程序实战教程01-需求分析

日常企业经常需要在网上拓展业务,通过互联网工具来宣传自己的产品。用户在看到企业宣传的内容后,如果有需要就会通过各种方式联系到企业。 为了方便的跟踪这些销售的机会,我们开发一款企业内部销售团队使用的小程序,便于管理潜在…

Win11的两个实用技巧系列之磁盘分区后再恢复的方法、调高进程的优先级方法

Win11磁盘分区后怎么恢复到分区前?Win11磁盘分区后在恢复的方法 很多人不知道win11磁盘分区怎么恢复回去?今日为你们带来的文章是win11磁盘分区的恢复方法,还有不清楚小伙伴和小编一起去学习一下吧 有不少小伙伴在使用电脑的时候经常会根据自身需求对其进行磁盘的…

【CSS】轮播图案例开发 ( 基本设置 | 子绝父相 | 浏览器水平居中 | 圆角设置 | 绝对定位居中设置 )

文章目录一、开发要点1、基本设置 - 取消默认内外边距 / 取消基本样式 / 图片自适应2、外层父容器设置 - 子绝父相 / 盒子浏览器水平居中 / 设置圆角 / 设置溢出隐藏3、左右按钮设置 - 绝对定位垂直居中设置 / 使用圆角矩形设置半圆 / 文字垂直居中4、底部小圆点设置 - 绝对定位…

Web_python_template_injection(Python模块注入)

打开链接,提示是Python的模块注入 我们先了解一些基本概念: 模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容…