设计模式_结构型模式 -《装饰器模式》

news2024/11/28 10:56:28

设计模式_结构型模式 -《装饰器模式》

笔记整理自 黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)

概述

我们先来看一个快餐店的例子。

快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。

image-20230107163114144

使用继承的方式存在的问题:

  • 扩展性不好

    如果要再加一种配料(火腿肠),我们就会发现需要给FriedRice和FriedNoodles分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类。

  • 产生过多的子类

定义

  • 指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

结构

装饰器模式 (Decorator Pattern) 中的角色:

  • 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色:继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。这个角色就是装饰者
  • 具体装饰(Concrete Decorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

案例

我们使用装饰器模式对快餐店案例进行改进,体会装饰器模式的精髓。

类图如下:

  • FastFood 就是抽象构件角色;
  • FriedRice(炒饭)和 FriedNoodles(炒面)就是具体构件角色;
  • Garnish(配料)就是抽象装饰角色,也就是最重要的装饰者,继承 FastFood,同时又聚合 FastFood;
  • 而 Egg(鸡蛋)和 Bacon(培根)就是具体装饰角色。

代码如下:

  • 抽象构件角色-快餐接口

    public abstract class FastFood {
        private float price; // 价格 
        private String desc; // 描述
    
        public FastFood() {
        }
    
        public FastFood(float price, String desc) {
            this.price = price;
            this.desc = desc;
        }
    
        public void setPrice(float price) {
            this.price = price;
        }
    
        public float getPrice() {
            return price;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        public abstract float cost(); // 获取价格
    }
    
  • 具体构件角色-炒饭

    public class FriedRice extends FastFood {
    
        public FriedRice() {
            super(10, "炒饭");
        }
    
        public float cost() {
            return getPrice();
        }
    }
    
  • 具体构件角色-炒面

    public class FriedNoodles extends FastFood {
    
        public FriedNoodles() {
            super(12, "炒面");
        }
    
        public float cost() {
            return getPrice();
        }
    }
    
  • 抽象装饰角色(装饰者类)- 配料类

    public abstract class Garnish extends FastFood {
    
        // 聚合-声明快餐类的变量
        private FastFood fastFood;
    
        public FastFood getFastFood() {
            return fastFood;
        }
    
        public void setFastFood(FastFood fastFood) {
            this.fastFood = fastFood;
        }
    
        public Garnish(FastFood fastFood, float price, String desc) {
            super(price, desc);
            this.fastFood = fastFood;
        }
    }
    
  • 具体装饰角色-鸡蛋配料

    public class Egg extends Garnish {
    
        public Egg(FastFood fastFood) {
            super(fastFood, 1, "鸡蛋");
        }
    
        public float cost() {
            return getPrice() + getFastFood().getPrice();
        }
    
        @Override
        public String getDesc() {
            return super.getDesc() + getFastFood().getDesc();
        }
    }
    
  • 具体装饰角色-培根配料

    public class Bacon extends Garnish {
    
        public Bacon(FastFood fastFood) {
            super(fastFood, 2, "培根");
        }
    
        @Override
        public float cost() {
            return getPrice() + getFastFood().getPrice();
        }
    
        @Override
        public String getDesc() {
            return super.getDesc() + getFastFood().getDesc();
        }
    }
    
  • 测试类

    public class Client {
        public static void main(String[] args) {
            // 点一份炒饭
            FastFood food = new FriedRice();
            // 花费的价格
            System.out.println(food.getDesc() + " " + food.cost() + "元");
    
            System.out.println("========");
            // 点一份加鸡蛋的炒饭
            FastFood food1 = new FriedRice();
    
            food1 = new Egg(food1);
            // 花费的价格
            System.out.println(food1.getDesc() + " " + food1.cost() + "元");
    
            System.out.println("========");
            // 点一份加培根的炒面
            FastFood food2 = new FriedNoodles();
            food2 = new Bacon(food2);
            // 花费的价格
            System.out.println(food2.getDesc() + " " + food2.cost() + "元");
        }
    }
    

    输出

    炒饭 10.0元
    ========
    鸡蛋炒饭 11.0元
    ========
    培根炒面 14.0元
    

好处

  • 装饰器模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰器对象来获取具有不同行为状态的多样化的结果。装饰器模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰器则是动态的附加责任。

  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰器模式可以动态扩展一个实现类的功能。

使用场景

  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
    • 不能采用继承的情况主要有两类:
      • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
      • 第二类是因为类定义不能继承(如 final 类)
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

总结

  • 装饰器设计模式优点
    • 装饰模式与继承关系的⽬的都是要扩展对象的功能,但装饰模式可以提供⽐继承更多的灵活性
    • 使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,原有代码无须改变,符合 “开闭原则”
  • 装饰器设计模式缺点
    • 装饰模式增加了许多⼦类,如果过度使⽤会使程序变得 很复杂(多层包装)。
    • 增加系统的复杂度,加⼤学习与理解的难度。

JDK源码解析-IO流

IO 流中的包装类使用到了装饰器模式:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。

我们以 BufferedWriter 举例来说明,先看看如何使用 BufferedWriter:

public class Demo {
    public static void main(String[] args) throws Exception {
        // 创建BufferedWriter对象
        // 创建FileWriter对象
        FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
        BufferedWriter bw = new BufferedWriter(fw);

        // 写数据
        bw.write("hello Buffered");

        bw.close();
    }
}

使用起来感觉确实像是装饰器模式,接下来看它们的结构:

BufferedWriter 继承自 Writer 同时又聚合了 Writer,这就是装饰器模式。

小结:

BufferedWriter 使用装饰器模式对 Writer 子实现类进行了增强,添加了缓冲区 Buffer,提高了写数据的效率。

代理模式和装饰器模式的区别

静态代理和装饰器模式的区别:

  • 相同点
    • 都要实现与目标类相同的业务接口
    • 在两个类中都要声明目标对象
    • 都可以在不修改目标类的前提下增强目标方法
  • 不同点
    • 目的不同
      • 装饰器是为了增强目标对象
      • 静态代理是为了保护和隐藏目标对象
    • 获取目标对象构建的地方不同
      • 装饰器是由外界传递进来,可以通过构造方法传递
      • 静态代理是在代理类内部创建,以此来隐藏目标对象

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

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

相关文章

PowerDesigner设计表时显示注释列Comment

首先,使用 PowerDesigner 新建模型,File —> New Model 然后,切换到模型类型(Model types)选项卡,选中 Physical Diagram 然后点击右侧表格图标,在左侧面板中创建表格如下。双击表格,找到Columns选项卡…

招标采购中,如何编写有效的RFI(信息邀请书)?

在企业招标采购过程中,RFI(信息邀请书)是一个从商品或服务的潜在供应商处收集信息的正式流程。RFI旨在由客户编写并发送给潜在供应商。RFI通常是第一个也是最广泛的一系列请求,旨在缩小潜在供应商候选人名单。 当企业对潜在供应…

【实际开发07】- XxxxController 批量处理 × 5 -【model】

目录 1. Mode 1. IotTypeController 基础 7 tips 2. 辅助添加 Validated 无法覆盖的 参数校验 1. 预处理空指针异常 ( 校验 : 核心必填参数 not null ) 3. RequestBody 对应API 存在 feign 调用时 , 注意 : 不可缺省 1. feign API 需要加 RequestBody , Controller 层可…

手工测试 | 黑盒测试方法论—边界值

本文节选自霍格沃兹测试学院内部教材 边界值分析法是一种很实用的黑盒测试用例方法,它具有很强的发现故障的能力。边界值分析法也是作为对等价类划分法的补充,测试用例来自等价类的边界。 这个方法其实是在测试实践当中发现,Bug 往往出现在定…

OpenCV4.6 VS 4.7 QRCode解码功能效果对比

导 读 本文主要对OpenCV4.7.0和4.6.0中QRCode检测/解码功能做简单的测试对比,供大家参考。 背景介绍 最近OpenCV更新到了4.7.0版本,在ChangeLog算法部分除了新增Stackblur滤波算法(详细介绍见下面链接),还有对QRCode检测和解码的改进。 吊打…

实现通讯录(C语言)

功能实现 实现步骤: 创建通讯录 初始化通讯录 打印菜单 实现选择功能 实现添加功能 实现删除功能 实现查找功能 实现修改功能 实现清空功能 实现排序功能 实现查询所有联系人信息功能 通讯录总代码 创建通讯录 1、创建成员信息结构体 我们用结构体来封装一个联系人…

【系列04】数组 多维数组 冒泡排序 稀疏数组[有目录]

数组声明和创建 变量类型 变量名称 变量的值 声明数组 int []nums;//定义//上面是首选 /*也可以 int nums2[];*/下面这种写法是为了迎合c与c用户的喜爱,更推荐用上面的定义 方式 创建数组 numsnew int[10];定义数组计算数组的和 package com.SunAo.array; //变量类型 变量名称…

TypeScript 数据模型层编程的最佳实践

虽然 TypeScript 主要用于客户端,而数据模型的设计主要是服务端来做的。 但是要写出优雅的代码,也还是有不少讲究的。 让我们从一个简单的我的文章列表 api 返回的数据开始,返回的文章列表的信息如下: {"id": 2018,&qu…

原生PHP及thinkphp6接入阿里云短信

申请accesskey获取到Accesskey ID和Accesskey Secret保存下来,一会要用到添加测试手机号,在接口测试能否正常发送下载阿里云短信sdk,使用composer下载,没有安装请先安装安装可以安装到任意文件夹下,后面代码写好后&…

TDSQL的安装教程(低配体验)

一、了解TDSQL tdsql腾讯云文档 TDSQL-C MySQL 版(TDSQL-C for MySQL)是腾讯云自研的新一代云原生关系型数据库。融合了传统数据库、云计算与新硬件技术的优势,为用户提供具备极致弹性、高性能、海量存储、安全可靠的数据库服务。TDSQL-C My…

AtCoder Beginner Contest 285解题报告

A - Edge Checker 2 Problem Statement Determine if there is a segment that directly connects the points numbered a and b in the figure below. Constraints 1≤a<b≤15a and b are integers.Input The input is given from Standard Input in the following for…

用SpectorJS调试WebGL应用

随着使用 WebGL 构建的体验不断涌现&#xff0c;以及 WebVR/AR 领域的所有改进&#xff0c;拥有高效的调试工具变得至关重要。 无论你是刚刚起步还是已经是使用 WebGL 开发 3D 应用程序的经验丰富的开发人员&#xff0c;都可能知道工具对于生产力的重要性。 在寻找此类工具时&…

【开发环境】JRE 裁剪 ① ( 裁剪 bin 目录下的 dll 动态库文件 )

文章目录一、JRE 裁剪二、裁剪 bin 目录下的 dll 动态库文件参考博客 : 精简jre1.8精简jre步骤裁剪JRE(嵌入式设备的java环境移植) 资源下载地址 : https://download.csdn.net/download/han1202012/87388400 一、JRE 裁剪 在 【IntelliJ IDEA】使用 exe4j 生成 jre jar 可执…

华为MPLS跨域A、B方案实验配置

目录 MPLS域内配置 MPLS-AS100域内配置 MPLS-AS200域内配置 域间方式A配置 ASBR4和ASBR5配置实例 ASBR之间建立基于实例的EBGP邻居关系 域间方式B配置 ASBR相连接口开启MPLS ASBR之间建立MP-BGP的EBGP邻居 配置取消RT值检测 配置传递路由时更改下一跳为自身 MPLS域内…

程序员必知必会 QPS TPS、URI URL、PV UV GMV

一、QPS和 TPS QPS&#xff1a;Queries Per Second&#xff0c;意思是“每秒查询数”&#xff0c;是一台服务器每秒能够响应的查询次数&#xff0c;是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。 即最大吞吐能力。 TPS&#xff1a;TransactionsPerSecond&…

springboot整合log4j2

导入依赖 <dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version> </dependency><!--log4j2--> <dependency><groupId>org.apache.logging.log4j</groupId>&…

Spring Boot(五十三):SpringBoot Actuator之实现

1 场景介绍 对于一个大型的几十个、几百个微服务构成的微服务架构系统&#xff0c;在线上时通常会遇到下面一些问题&#xff0c;比如&#xff1a; 1. 如何知道哪些服务除了问题&#xff0c;如何快速定位&#xff1f; (健康状况&#xff09; 2. 如何统一监控各个微服务的性能指标…

JAVA会员营销系统源码+数据库,实体店铺会员管理和营销系统源码,采用SpringBoot + Mysql+Mybatis

会员营销系统介绍 介绍 fuint会员营销系统是一套开源的实体店铺会员管理和营销系统。系统基于前后端分离的架构&#xff0c;后端采用Java SpringBoot Mysql&#xff0c;前端基于当前流行的Uniapp&#xff0c;Element UI&#xff0c;支持小程序、h5。主要功能包含电子优惠券、…

冰蝎V4.0流量分析到攻防检测

0x01 前言 最近在改写 yso&#xff0c;觉得自己基础太差了&#xff0c;想先阅读一下 sqlmap、冰蝎以及一些其他工具的开发思路。文章可能写的不够严谨&#xff0c;有不对的地方还请师傅们多多指出。 0x02 环境搭建 这里我看的是 MountCloud 师傅所二开的冰蝎项目&#xff0c…

【关于Linux中----进程间通信方式之system V共享内存】

文章目录一、共享内存示意图二、学习共享内存前的准备工作三、共享内存函数3.1创建共享内存&#xff1a;3.2控制共享内存&#xff1a;3.3挂接和去挂接&#xff1a;一、共享内存示意图 上一篇文章中讲述的是管道的通信方式&#xff0c;而这里要讲的是操作系统层面专门为进程间通…