当前位置: 首页 > news >正文

《设计模式》装饰模式

1.装饰模式定义

装饰模式(Decorator) ˈdekəreɪtər:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活

1.1 UML图:


共计4个对象:

  • Component(组件): 定义了一个抽象接口,用于具体组件和装饰器共享。
  • ConcreteComponent(具体组件): 实现了Component接口的具体类,是被装饰的对象。
  • Decorator(装饰器): 也实现了Component接口,并持有一个Component对象的引用,这是装饰的核心。
  • ConcreteDecorator(具体装饰器): 扩展了Decorator类,负责具体的装饰操作。

1.2 核心代码:

package decorator.pattern.basedemo;public class BaseDemo {public static void main(String[] args){System.out.println("**********************************************");System.out.println("decorator basedemo");System.out.println();ConcreteComponent c = new ConcreteComponent();ConcreteDecoratorA d1 = new ConcreteDecoratorA();ConcreteDecoratorB d2 = new ConcreteDecoratorB();// 首先用d1来包装cd1.SetComponent(c);//再用有来包装d1d2.SetComponent(d1);// 执行顺序 c d1 d2 d2.Operation();   System.out.println();System.out.println("**********************************************");}
}//Component类
abstract class Component {public abstract void Operation();
}//ConcreteComponent类
class ConcreteComponent extends Component {public void Operation() {System.out.println("具体对象:");}}//Decorator类
abstract class Decorator extends Component {protected Component component;//装饰一个Component对象public void SetComponent(Component component) {this.component = component;}//重写Operation(),实际调用component的Operation方法public void Operation() {if (component != null) {component.Operation();}}
}//ConcreteDecoratorA类
class ConcreteDecoratorA extends Decorator {private String addedState;//本类独有子段,以区别于ConcreteDecoratorB类public void Operation() {super.Operation();//首先运行了原有Component的Operation()this.addedState = "具体装饰对象A的独有操作";//再执行本类独有功能System.out.println(this.addedState);}
}//ConcreteDecoratorB类
class ConcreteDecoratorB extends Decorator {public void Operation() {super.Operation();//首先运行了原有Component的Operation()this.AddedBehavior();//再执行本类独有功能}//本类独有方法,以区别于ConcreteDecoratorA类private void AddedBehavior() {System.out.println("具体装饰对象B的独有操作");}
}

执行结果:
在这里插入图片描述

2.装饰模式举例:

业务场景:需要实现一个商场收银系统,有三种策略,

  1. 正常结账
  2. 打折
  3. 满减
  4. 先打折再满减

2.1 代码设计UML图如下:

在这里插入图片描述

2.2 核心代码:

Isale接口

public interface ISale {public double acceptCash(double price,int num);}

CashNormal:

public class CashNormal implements ISale {//正常收费,原价返回public double acceptCash(double price,int num){return price * num; }    
}

CashSuper

public class CashSuper implements ISale {protected ISale component;//装饰对象public void decorate(ISale component) {this.component=component;}public double acceptCash(double price,int num){double result = 0d;if (this.component != null){//若装饰对象存在,则执行装饰的算法运算result = this.component.acceptCash(price,num);    }return result;}
}

CashRebate

public class CashRebate extends CashSuper {private double moneyRebate = 1d;//打折收费。初始化时必需输入折扣率。八折就输入0.8public CashRebate(double moneyRebate){this.moneyRebate = moneyRebate;}//计算收费时需要在原价基础上乘以折扣率public double acceptCash(double price,int num){double result = price * num * this.moneyRebate;return super.acceptCash(result,1);}}

CashReturn

public class CashReturn extends CashSuper {private double moneyCondition = 0d; //返利条件private double moneyReturn = 0d;    //返利值//返利收费。初始化时需要输入返利条件和返利值。//比如“满300返100”,就是moneyCondition=300,moneyReturn=100public CashReturn(double moneyCondition,double moneyReturn){this.moneyCondition = moneyCondition;this.moneyReturn = moneyReturn;}//计算收费时,当达到返利条件,就原价减去返利值public double acceptCash(double price,int num){double result = price * num;if (moneyCondition>0 && result >= moneyCondition)result = result - Math.floor(result / moneyCondition) * moneyReturn; return super.acceptCash(result,1);   }}

客户端测试类:

import java.util.Scanner;public class demotest {public static void main(String[] args){System.out.println("**********************************************");		System.out.println("装饰模式");System.out.println();		int discount = 0; 		//商品折扣模式double price = 0d; 		//商品单价int num = 0;			//商品购买数量double totalPrices = 0d;//当前商品合计费用double total = 0d;		//总计所有商品费用Scanner sc = new Scanner(System.in);do {System.out.println("商品折扣模式如下:");	System.out.println("1.正常收费");	System.out.println("2.打八折");	System.out.println("3.打七折");	System.out.println("4.满300送100");	System.out.println("5.先打8折,再满300送100");	System.out.println("6.先满200送50,再打7折");	System.out.println("请输入商品折扣模式:");	discount = Integer.parseInt(sc.nextLine());System.out.println("请输入商品单价:");	price = Double.parseDouble(sc.nextLine());System.out.println("请输入商品数量:");	num = Integer.parseInt(sc.nextLine());System.out.println();	if (price>0 && num>0){//根据用户输入,将对应的策略对象作为参数传入CashContext对象中CashContext cc = new CashContext(discount);//通过Context的getResult方法的调用,可以得到收取费用的结果//让具体算法与客户进行了隔离totalPrices = cc.getResult(price,num);total = total + totalPrices;System.out.println();	System.out.println("单价:"+ price + "元 数量:"+ num +" 合计:"+ totalPrices +"元");	System.out.println();System.out.println("总计:"+ total+"元");	System.out.println();}}while(price>0 && num>0);System.out.println();System.out.println("**********************************************");}
}

输出结果:
在这里插入图片描述

3. 装饰模式的优缺点;

  • 优点:
    • 采用装饰模式扩展对象的功能比采用继承方式更加灵活。
    • 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
  • 缺点:
    • 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。

4.装饰模式的应用场景

类纵向层次比较多时适用。
比如 人的装饰 上衣,下衣,鞋子,三种抽象装饰类, 各有子类,如果采用继承,就会产生 上衣数 * 下衣数*鞋子数 个子类,会导致子类数量爆炸。 装饰模式则写成独立的子类,根据具体情况使用即可。

  • 动态地添加或修改对象的功能

    • 当需要动态地为一个对象添加额外的功能,而且希望这些功能可以灵活组合时,装饰模式是一个很好的选择。这样可以避免使用大量子类来实现所有可能的组合,而是使用装饰器来动态地添加这些功能。
  • 避免使用继承导致的类爆炸

    • 经常会发现在类的层次结构中添加新功能导致的子类爆炸问题。装饰模式通过将功能分离到单独的装饰器类中,避免了这种情况的发生。
  • 保持类的简单性和单一责任原则

    • 使用装饰模式可以将一些复杂的功能分离到单独的装饰器类中,使得原始类保持简单和具有单一职责。
  • 在运行时动态地添加或删除功能

    • 装饰模式允许在运行时动态地添加或删除对象的功能,这对于某些情况下的配置和扩展非常有用。

经典使用方案

Java I/O库中的输入输出流

装饰模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。基本的InputStream或OutputStream可以通过添加额外的功能,比如缓冲、加密或压缩等,而无需修改它们的代码。
例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。

下面代码是为 FileReader 增加缓冲区而采用的装饰类 BufferedReader 的例子:

	BufferedReader in=new BufferedReader(new FileReader("filename.txtn));String s=in.readLine();
GUI界面组件

在GUI编程中,经常需要动态地添加新的功能或外观到用户界面组件上。比如,一个简单的文本框可以通过装饰模式来添加滚动条、边框、背景色等功能,而无需修改原始文本框类的代码。

Web开发中的过滤器

在Web开发中,过滤器常常用于对请求或响应进行处理,比如身份验证、日志记录、数据压缩等。使用装饰模式可以轻松地添加新的过滤功能,同时保持代码的灵活性和可维护性。

5.装饰模式扩展

装饰模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的,如以下两种情况。
装饰者模式的简化
1.去掉接口的形式,直接继承自要被装饰的类即可。
在这里插入图片描述

2.直接使用实现接口的形式实现装饰,而不用再额外加一层继承关系。适用于只有一个强化关系的情况
在这里插入图片描述
透明度的要求:
装饰者模式要求程序不应该声明需要被装饰的实体类,而是应该声明抽象接口。

半透明的装饰模式:
当发现工人接口并不能满足所有的要求的时候,要想实现透明度要求,必须在接口中添加新方法,所以很多实现的装饰者模式都是采取“半透明”的方式,即装饰者类可以对接口进行拓展,同时声明的时候,可以选择以装饰者类为准。 就是不在Component接口中增加方法,而是在装饰者类中进行方法扩展。

6. 总结

动态地将职责动态附加到对象上。想要扩展功能, 装饰者提供有别于继承的另一种选择。

要点
1、继承属于扩展形式之一,但不见得是达到弹性设计的最佳方案。
2、在我们的设计中,应该允许行为可以被扩展,而不须修改现有的代码。
3、组合和委托可用于在运行时动态地加上新的行为。
4、除了继承,装饰者模式也可以让我们扩展行为。
5、装饰者模式意味着一群装饰者类, 这些类用来包装具体组件。
6、装饰者类反映出被装饰的组件类型(实际上,他们具有相同的类型,都经过接口或继承实现)。
7、装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
8、你可以有无数个装饰者包装一个组件。
9、 装饰者一般对组建的客户是透明的,除非客户程序依赖于组件的具体类型。

7.参考

  • https://cloud.tencent.com/developer/article/1369799
  • https://cloud.tencent.com/developer/article/1154778?policyId=1004
  • https://blog.csdn.net/weixin_40026739/article/details/136226138
http://www.dtcms.com/a/334820.html

相关文章:

  • python日志新姿势,Loguru库的应用
  • 后台管理系统-3-vue3之左侧菜单栏和头部导航栏的静态搭建
  • VUE+SPRINGBOOT从0-1打造前后端-前后台系统-用户管理
  • 力扣(LeetCode) ——142. 环形链表 II(C语言)
  • [Python]PTA:实验2-3-1-for 求1到100的和
  • ComfyUI-3D-Pack:3D创作的AI神器
  • 深度强化学习是否违背样本独立原则?
  • 使用IntersectionObserver实现页面右侧运营位区域固定,和页面列表数据分页加载
  • CSS复习
  • Euler 采样器在扩散模型中的原理解析:从数学公式到实践应用
  • Apereo CAS靶场渗透练习
  • 【Linux系统】进程间通信:System V IPC——共享内存
  • Flink 源码系列 - 前言
  • 如何使用5个时间步长创建移动平均特征
  • 自动驾驶中的传感器技术33——Lidar(8)
  • Leetcode 14 java
  • 数论之普通判别法、埃氏筛与线性筛的应用及其对比
  • PowerShell 第11章:过滤和比较(下)
  • 深度剖析Redisson分布式锁项目实战
  • redis存储原理与对象模型
  • 《A Practical Guide to Building Agents》文档学习
  • 数学建模:智能优化算法
  • PostgreSQL——事务处理与并发控制
  • CVE-2021-4300漏洞复现
  • 海康机器人3D相机的应用
  • ZKmall开源商城的数据校验之道:用规范守护业务基石
  • Vue 3与React内置组件全对比
  • 【lucene】SegmentInfos
  • 《Leetcode》-面试题-hot100-技巧
  • 科研工具的一些注意事项