在传统的软件开发过程中,如果想对某一组件扩展相应的功能,首先想到的可能是继承的方式来实现。但是如果要增加一系列的功能的话,随着功能的增多。子类就会很膨胀,而且继承的方式耦合性很高。不便于我们代码后续的扩展,也违背了我们的开闭原则。
举个生活中的例子,我们去奶茶店点杯原味奶茶,如果我们想要加自己喜欢的料,比如有的人可能喜欢红豆,有的人喜欢珍珠,有的人两样都加…;这种配料组合搭配的方式很多。而且只是对我一杯奶茶上增加辅料,最后我得到的(对象)还是这杯奶茶。
有没有一种方式,让我们的程序代码,也像珍珠奶茶一样加珍珠,而不是我选一种口味,就需要重新下单来买一杯,来动态的增加一些能力呢。在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现,下面介绍的装饰器就能实现此种需求。
装饰器模式的结构与实现
- 模式的结构
装饰器模式主要包含以下角色。
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
装饰器模式的结构图如下:
代码示例:
就按照开头举买奶茶的例子,我们通过装饰器模式,来帮助我们买到多重组合的奶茶
- 抽象构件 (奶茶构件)
public abstract class AbstractMilkyTea {
/**
* 单品信息
*/
private String desc;
/**
* 价格
*/
private float price;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
/**
* 单品价格
* @return
*/
protected abstract float cost();
}
- 具体构件(原味珍珠奶茶)
public class PearlMilkyTea extends AbstractMilkyTea{
public PearlMilkyTea() {
setDesc("珍珠");
setPrice(10);
}
@Override
protected float cost() {
return getPrice();
}
}
- 抽象装饰
public abstract class Decorator extends AbstractMilkyTea {
public AbstractMilkyTea abstractMilkyTea;
public Decorator(AbstractMilkyTea abstractMilkyTea) {
this.abstractMilkyTea = abstractMilkyTea;
}
/**
* 当前总价格
*/
@Override
protected float cost() {
return abstractMilkyTea.cost() + super.getPrice();
}
@Override
public String getDesc() {
return abstractMilkyTea.getDesc() + "&" + super.getDesc();
}
}
- 具体构件(红豆)
public class RedBeans extends Decorator {
public RedBeans(AbstractMilkyTea abstractMilkyTea) {
super(abstractMilkyTea);
}
@Override
public float cost() {
return super.cost() + 5.0f;
}
@Override
public String getDesc() {
return abstractMilkyTea.getDesc() + "红豆";
}
}
- 具体构件(布丁)
public class Pudding extends Decorator {
public Pudding(AbstractMilkyTea abstractMilkyTea) {
super(abstractMilkyTea);
setDesc("布丁");
setPrice(6.5f);
}
@Override
public float cost() {
return super.cost();
}
@Override
public String getDesc() {
return super.getDesc();
}
}
- client(买奶茶)
public class Client {
public static void main(String[] args) {
//珍珠奶茶
PearlMilkyTea pearlMilkyTea = new PearlMilkyTea();
System.out.println("单独买珍珠奶茶价格:" + pearlMilkyTea.cost());
//加料
//1.加一份红豆(红豆珍珠奶茶)
RedBeans redBeansPearlMilkyTea = new RedBeans(pearlMilkyTea);
System.out.println("珍珠奶茶+红豆:【" + redBeansPearlMilkyTea.getDesc() + "】 价格 :" + redBeansPearlMilkyTea.cost());
//2.再加一份布丁
Pudding pudding = new Pudding(redBeansPearlMilkyTea);
System.out.println("珍珠奶茶+红豆+布丁:【"+pudding.getDesc()+"】 价格: "+ pudding.cost());
}
}
执行结果:
单独买珍珠奶茶价格:10.0
珍珠奶茶+红豆:【珍珠红豆】 价格 :15.0
珍珠奶茶+红豆+布丁:【珍珠红豆&布丁】 价格: 21.5
这里如果想喝原味的,可以不加任何辅料,就10块钱。那我对这杯奶茶再加一份红豆,只用多付红豆的钱,包括后面的布丁。这个案例就是装饰器的简单应用。
代码案例结构图:
对照我们前面话的结构图,结合案例,每个类所扮演的角色,方便理解。
装饰器模式的应用场景
装饰器模式通常在以下几种情况使用。
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
装饰器模式的优缺点
装饰器模式的主要优点有:
- 装饰器模式比继承更加灵活,而且耦合性低;
- 装饰器可以动态的对某些能力按需给被装饰组件赋能,可以自由组合;
- 装饰器模式完全遵守开闭原则
缺点:
- 装饰器模式会增加许多子类,过度使用会增加程序得复杂性。
- 代码阅读性不是很高,要理解装饰器模式。
装饰器模式在源码中的应用
1.装饰器模式在JDK源码中的应用(IO)
JDK中使用到装饰器的类未IO体系相关的类,例如InputStream
、OutputStream
、BufferedReader
等。下面看一下JDK中输入流的结构图,这里以输入流为例:
这里面InputStream
就扮演抽象构件的角色,而他下面FileInputStream
,ByteArrayInputStream
,则为具体构件(被装饰者),FilterInputStream
继承InputStream
,为装饰者,其下面的BufferedInputStream
,DataInputStream
为具体装饰者。
源码分析:
截取主要部分
- InputStream 抽象构件
public abstract class InputStream implements Closeable {
public int read(byte b[], int off, int len) throws IOException {}
}
FileInputStream
被装饰者
继承InputStream
public
class FileInputStream extends InputStream
{
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
// operation()...
FilterInputStream
装饰器 继承InpuStream
public
class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
BufferedInputStream
具体装饰器
public class BufferedInputStream extends FilterInputStream {
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
// 调用父类 初始化被建造者
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
}
同为装饰器的DataInputStream
同BufferedInputStream
一样。
- 使用方
DataInputStream in=new DataInputStream(new BufferedInputStream(new FileInputStream("/Users/fengwei/abt.txt")));
日常使用的,从指定目录获取文件输入流,再通过缓冲输入流封装,再把缓冲输入流传递至 数据输入流DataInputStream
,其实每经过一层装饰器封装,得到的也是个被装饰者
,还可以继续包装。
装饰器模式在Spring源码中的应用
在 Spring 中,TransactionAwareCacheDecorator
类相当于装饰器模式中的抽象装饰角色,主要用来处理事务缓存,代码如下。
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
/**
* Create a new TransactionAwareCache for the given target Cache.
* @param targetCache the target Cache to decorate
*/
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
/**
* Return the target Cache that this Cache should delegate to.
*/
public Cache getTargetCache() {
return this.targetCache;
}
......
}
TransactionAwareCacheDecorator
就是对 Cache 的一个包装
下面再来看一个 MVC 中的装饰器模式:HttpHeadResponseDecorator 类,相当于装饰器模式中的具体装饰角色。
public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator{
public HttpHeadResponseDecorator(ServerHttpResponse delegate){
super(delegate);
}
...
}
✨✨ 欢迎🔔订阅个人的微信公众号 享及时博文更新