type
status
date
slug
summary
tags
category
icon
password
 

一、装饰器概述及结构

 
装饰器模式是对功能的增强,而不是附加新的功能。代理模式才是附加新的功能
 
装饰器结构:
  • 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。如下方的 Drink 类。
  • 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。如下方的 LongBlack(美式咖啡)、Decaf(无因咖啡) 等。
  • 抽象装饰(Decorator)角色 : 装饰者,继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。如下方的 Decorator 类。
  • 具体装饰(Concrete Decorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。如下方的咖啡添加品类,Chocolate Milk 类等。
 
装饰器模式就是简单的“用组合替代继承”吗?
 
第一个比较特殊的地方是:装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。
第二个比较特殊的地方是:装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。
 
咖啡案例:
  1. 咖啡种类:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
  1. 调料:Milk、Soy(豆浆)、Chocolate
  1. 方便计算不同种类咖啡的费用:客户可以单点咖啡,也可以单品咖啡+调料组合。
notion image
 
我们是否可以只需在 Chocolate 等类中重新实现那些需要重写的函数就可以了,其他函数继承 Drink 的默认实现?
这样做是行不通的!
对于即便是不需要增强功能的函数来说(假如Drink有其他方法),Chocolate 还是必须把它重新实现一遍,简单包裹对 Drink 对象的函数调用。如果不重新实现,那 Chocolate 类就无法将最终的任务,委托给传递进来的 Drink 对象来完成。略有抽象…..
其实这样抽象出一个中间层,既是为了功能正常也是为了将重复性方法在父类只实现一遍就好。

二、装饰器模式实现

 

抽象构件(Component)角色

 
这里指饮品 Drink 抽象类,费用方法需要具体子类进行实现,子类包括咖啡类型和调味品类型。
  • 抽象类中的两个set方法,子类也需要进行重写设置。
 
 

抽象装饰(Decorator)角色

 
装饰者包含被装饰者。
装饰角色作为咖啡的装饰组合类型,是各种调味品的父类。
  • 构造器会传入单品咖啡或者加了调味品的咖啡,重写的 cost 方法会将:
    • 传入的咖啡价格(包括到目前为止加了的所有调味品价格) obj.cost 加上
    • 自己调味品价格(调味品价格为构造器中调用父类方法set到了顶层Drink类) super.getPrice
    • 即这里重写父类 cost 方法得到的就是总价。供 coffee 父类调用父类 Drink 类获取总价格。
这里的 cost 方法计算其实类似递归计算。
 
 

Coffee 类

 
Coffee 类 作为 抽象构件(Component)角色 和 具体构件(Concrete Component)角色 之间的一层,用于抽取公共部分,如下方可以抽取出一个 cost 方法,避免子类都进行重写。
  • 当创建的是单品咖啡时,获取价格就是调用这里Coffee父类的 cost 方法,进而调用到顶层 Drink 中保存的单品价格。
  • 当创建的是加了调味品的咖啡,调用 cost 方法将不是这里的方法,而是装饰器 Decorator 的 cost 方法,该方法会调用到单品咖啡的 cost 方法,即这里重写的方法,以及父类 Drink 中保存的调味品价格。
  • 当创建的是嵌套加了多种调味品的咖啡,调用 cost 方法将不是这里的方法,而是装饰器 Decorator 的 cost 方法,该方法会调用到最后一次调味品的价格 super.getPrice,以及加了其他所有调味品咖啡的价格(这是一个聚合,类似递归,这里会再次调用倒数第二次调味品的价格 super.price 以及 加了其他调味品的咖啡的价格 …. 最后会调用到单品咖啡的价格,即下方重写的cost方法)
 

具体构件(Concrete Component)角色

 
具体咖啡类型,构造器中调用父类 Drink 构建的set方法将自己价格设置到父类中。
 
 

具体装饰(Concrete Decorator)角色

 
具体装饰调味品类,构造器中传入饮品(单品咖啡或加了调味品的咖啡)
调用父类 Drink 类 set 方法,将当前调味品价格设置到父类中。
 
 

测试类-咖啡店

 
调味品构造器中加入咖啡,得到的咖啡可以继续加入到其他调味品构造器中。
装饰器类调味品类和原始类单品咖啡继承同样的父类 Drink,这样我们可以对原始类“嵌套”多个装饰器类。
 
 
 

三、装饰器案例 - Java IO 流

 
  1. InputStream 是抽象类, 类似我们前面讲的 Drink
  1. FileInputStream 是 InputStream 子类,类似我们前面的 DeCaf, LongBlack
  1. FilterInputStream 是 InputStream 子类:类似我们前面 的 Decorator 修饰者
  1. DataInputStream 是 FilterInputStream 子类,具体的修饰者,类似前面的 Milk, Soy 等
  1. FilterInputStream 类 有 protected volatile InputStream in; 即装饰者包含被装饰者
  1. 具体装饰者可以进行嵌套,如:
 
 
notion image
 
 
 
 
 
 
 
结构型模式之适配器设计模式结构型模式之桥接设计模式
Loading...
ITNXD
ITNXD
一个普通的干饭人🍚
最新发布
Java 并发编程
2025-7-31
Spring 源码系列第三章 - 后置 Bean 处理器与 Bean 生命周期
2022-12-25
Spring 源码系列第一章 - Spring 核心组件接口
2022-12-11
Spring 源码系列第二章 - 后置工厂处理器与 Bean 生命周期
2022-12-10
行为型模式之迭代器设计模式
2022-12-2
行为型模式之责任链设计模式
2022-12-1