这是我参与更文挑战的第16天,活动详情查看: 更文挑战
概念
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于设计模式中的结构型模式。其主要特点是装饰者模式动态地将责任附加到对象身上。若要扩展功能,装饰者提供了比继承更有弹性的扩展方式。
装饰者模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。对应的UML:
根据上面的类图我们总结一下在装饰模式中有哪些角色:
-
抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
-
具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。(也就是需要装饰的类)
-
装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
-
具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。
优缺点
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
- 增加设计模式的通用问题就是使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难。
代码示例
我们早上吃煎饼在地铁口大家可以看到很多人会在原来的基础煎饼上加加个肠加个鸡蛋还有加辣条的。但无论加什么他还是一个煎饼,只不过他的”内容丰富了”。我们就用代码模拟一下这个场景,”装饰”一下我们这个煎饼。
- 我们先定义一个抽象的煎饼
public interface Pancakes {
/**
* 煎饼的名字
*/
String pname();
}
复制代码
- 具体被装饰对象1杂粮煎饼
public class ZaliangPancakes implements Pancakes{
@Override
public String pname() {
System.out.println("我是杂粮煎饼");
return null;
}
}
复制代码
- 具体装饰对象2红豆煎饼
public class HongDouPancakes implements Pancakes{
@Override
public String pname() {
System.out.println("我是红豆煎饼");
return null;
}
}
复制代码
- 定义装饰类
public class DecoratorPancakes implements Pancakes {
private Pancakes pancakes;
public DecoratorPancakes(Pancakes pancakes) {
this.pancakes = pancakes;
}
@Override
public String pname() {
//委派给具体的构建者
return pancakes.pname();
}
}
复制代码
- 具体的装饰类,加个烤肠
public class KaoChangPanvakes extends DecoratorPancakes {
public KaoChangPanvakes(Pancakes pancakes) {
super(pancakes);
}
@Override
public String pname() {
super.pname();
System.out.println("加个烤肠");
return null;
}
}
复制代码
- 具体的装饰类,加个鸡蛋
public class JiDanPanvakes extends DecoratorPancakes {
public JiDanPanvakes(Pancakes pancakes) {
super(pancakes);
}
@Override
public String pname() {
super.pname();
System.out.println("加个鸡蛋");
return null;
}
}
复制代码
- 写个客户端调用
public class Client {
public static void main(String[] args) {
Pancakes pancakes = new ZaliangPancakes();//选则杂粮煎饼
pancakes = new KaoChangPanvakes(pancakes); //加烤肠
System.out.println(pancakes.pname());
System.out.println("-----分割线------");
Pancakes pancakes2 = new ZaliangPancakes();//选则杂粮煎饼
pancakes2 = new JiDanPanvakes(pancakes2); //加鸡蛋
System.out.println(pancakes2.pname());
System.out.println("-----分割线------");
Pancakes pancakes3 = new ZaliangPancakes();//选则杂粮煎饼
pancakes3 = new KaoChangPanvakes(new JiDanPanvakes(pancakes3));//即加鸡蛋又加烤肠
System.out.println(pancakes3.pname());
}
}
复制代码
- 结果
我是杂粮煎饼
加个烤肠
—–分割线——
我是杂粮煎饼
加个鸡蛋
—–分割线——
我是杂粮煎饼
加个鸡蛋
加个烤肠
装饰模式的应用
我们平时开发最常见的装饰模式的应用就是java里的IO部分。由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰者模式,那么类的数目就会大大减少,性能的重复也可以减至最少。因此装饰者模式是Java I/O库的基本模式。我们看一下InputStream
这个类:
我们简单分析一下各个角色:
- 抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。
- 具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。
- 抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。
- 具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。
基于上面的分析我们在看一下我们经常用的InputStream对应的代码:
// 流式读取文件
DataInputStream dis = null;
try{
dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("a.txt")
)
);
//读取文件内容
byte[] bs = new byte[dis.available()];
dis.read(bs);
String content = new String(bs);
System.out.println(content);
}catch (Exception e) {
e.printStackTrace();
} finally{
dis.close();
}
复制代码
这个就比较熟悉了吧,最里层是一个FileInputStream对象,然后把它传递给一个BufferedInputStream对象,经过BufferedInputStream处理,再把处理后的对象传递给了DataInputStream对象进行处理,这个过程其实就是装饰器的组装过程,FileInputStream对象相当于原始的被装饰的对象,而BufferedInputStream对象和DataInputStream对象则相当于装饰器。