一、概念介绍
装饰者模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
在装饰者模式中,为了让系统具有更好的灵活性和可扩展性,我们通常会定义一个抽象装饰类,而将具体的装饰类作为它的子类
二、类图分析
-
AbstractComponent(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
-
ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
-
AbstractDecorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
-
ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
三、代码分析
- 煎饼抽象类
public abstract class ABattercake {
protected abstract String getDesc();
protected abstract int cost();
}
- 煎饼类,继承煎饼抽象类
public class Battercake extends ABattercake {
@Override
protected String getDesc() {
return "煎饼";
}
@Override
protected int cost() {
return 8;
}
}
- 抽象装饰类,需要注意的是,抽象装饰类通过成员属性的方式将 煎饼抽象类组合进来,同时也继承了煎饼抽象类,且这里定义了新的业务方法 doSomething()
public abstract class AbstractDecorator extends ABattercake {
private ABattercake aBattercake;
public AbstractDecorator(ABattercake aBattercake) {
this.aBattercake = aBattercake;
}
protected abstract void doSomething();
@Override
protected String getDesc() {
return this.aBattercake.getDesc();
}
@Override
protected int cost() {
return this.aBattercake.cost();
}
}
- 鸡蛋装饰类,继承抽象装饰类,鸡蛋装饰器在父类的基础上增加了一个鸡蛋,同时价格加上 1 块钱
public class EggDecorator extends AbstractDecorator {
public EggDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected void doSomething() {
}
@Override
protected String getDesc() {
return super.getDesc() + " 加一个鸡蛋";
}
@Override
protected int cost() {
return super.cost() + 1;
}
public void egg() {
System.out.println("增加了一个鸡蛋");
}
}
- 火腿装饰类,与鸡蛋装饰器类似,继承了抽象装饰类,给在父类的基础上加上一根香肠,同时价格增加 2 块钱
public class SausageDecorator extends AbstractDecorator{
public SausageDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected void doSomething() {
}
@Override
protected String getDesc() {
return super.getDesc() + " 加一根香肠";
}
@Override
protected int cost() {
return super.cost() + 2;
}
}
- 测试类
购买加两个鸡蛋和一根香肠的煎饼
public class Test {
public static void main(String[] args) {
ABattercake aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
aBattercake = new EggDecorator(aBattercake);
aBattercake = new SausageDecorator(aBattercake);
System.out.println(aBattercake.getDesc() + ", 销售价格: " + aBattercake.cost());
}
}
输出:
煎饼 加一个鸡蛋 加一个鸡蛋 加一根香肠, 销售价格: 12
- 类图
由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。
四、实际运用
1、使用场景
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
2、源码分析
I/O流中的装饰模式
java的IO流中的输入和输出,如下图所示;
由上图可知在Java中应用程序通过输入流(InputStream)的Read方法从源地址处读取字节,然后通过输出流(OutputStream)的Write方法将流写入到目的地址。
流的来源主要有三种:本地的文件(File)、控制台、通过socket实现的网络通信
下面的图可以看出Java中的装饰者类和被装饰者类以及它们之间的关系,这里只列出了InputStream中的关系
由上图可以看出只要继承了FilterInputStream的类就是装饰者类,可以用于包装其他的流,装饰者类还可以对装饰者和类进行再包装。
这里总结几种常用流的应用场景
下面看一下Java中包装流的实例:
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class StreamDemo {
public static void main(String[] args) throws IOException{
DataInputStream in=new DataInputStream(new BufferedInputStream(new FileInputStream("D:\\hello.txt")));
while(in.available()!=0) {
System.out.print((char)in.readByte());
}
in.close();
}
}
上面程序中对流进行了两次包装,先用 BufferedInputStream将FileInputStream包装成缓冲流也就是给FileInputStream增加缓冲功能,再DataInputStream进一步包装方便数据处理。
所以,如果需要手写一个包装流,需要继承装饰类filterInputStream
譬如来实现这样一个操作的装饰者类:将输入流中的所有小写字母变成大写字母
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class UpperCaseInputStream extends FilterInputStream {
protected UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toUpperCase(c));
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for (int i = off; i < off + result; i++) {
b[i] = (byte) Character.toUpperCase((char) b[i]);
}
return result;
}
public static void main(String[] args) throws IOException {
int c;
InputStream in = new UpperCaseInputStream(new FileInputStream("D:\\hello.txt"));
try {
while ((c = in.read()) >= 0) {
System.out.print((char) c);
}
} finally {
in.close();
}
}
}
五、总结与比较
装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化,以便增加新的具体构件类和具体装饰类。
代理和装饰的区别
代理模式中,代理类对被代理的对象有控制权,决定其执行或者不执行,本质是对对象行为的限制,常见的代理就是权限判断。而装饰模式中,装饰类对代理对象没有控制权,只能为其增加一层装饰,以加强被装饰对象的功能,是在增加方法,而且是动态的。
参考:
https://blog.csdn.net/hitprince/article/details/6794748
https://blog.csdn.net/wwwdc1012/article/details/82764333