装饰者模式
装饰者模式是23种设计模式之一,是指在不改变原来的类和使用继承的方式,动态的扩展这个类的功能。装饰者允许向一个现有的对象添加特定的功能却不改变它的结构。通过一个类来包装原有的类来提供额外的功能。
特点
装饰者模式的结构:

Component:抽象组件,被装饰的原始对象,可以是抽象类或者接口
ConcreatCompoent:实现或者继承了抽象组件,被装饰的具体的实现
Decorator:抽象的装饰者,实现或者继承了Component抽象组件,持有抽象组件的引用
ConcreatDecotarorA、ConcreatDecotarorB:具体的装饰者,实现或者继承了抽象装饰者。
实现
举个简单的例子,假设有一个科沃斯机器人,它的基本功能是扫地,现在要求这个机器人可以边扫地边唱歌,还可以边扫地边跳舞。
那么,就可以将机器定义为一个抽象的类,里面有一个抽象的方法,sweep();
/**
* 抽象组件
*/
public abstract class Robot {
/**
* 打扫的方法
*/
public abstract void sweep();
}
被装饰的具体对象:
/**
* 抽象组件的具体实现类,即被装饰者对象
*/
public class ConcreatRobot extends Robot {
@Override
public void sweep() {
System.out.println("打扫卧室");
}
}
抽象装饰者:
/**
* 抽象的装饰者类
*/
public class DecoratorRobot extends Robot {
private Robot robot;
public DecoratorRobot(Robot robot) {
this.robot = robot;
}
@Override
public void sweep() {
robot.sweep();
}
}
具体的装饰者A:
/**
* 装饰者A
*/
public class ConcreatDecoratorA extends DecoratorRobot {
public ConcreatDecoratorA(Robot robot) {
super(robot);
}
/**
* 一遍扫地一边唱歌
*/
@Override
public void sweep() {
super.sweep();
this.sign();
}
/**
* 唱歌
*/
public void sign(){
System.out.println("唱一首单身情歌");
}
}
具体的装饰者B:
/**
* 装饰者B
*/
public class ConcreatDecoratorB extends RobotDecorator {
public ConcreatDecoratorB(Robot robot) {
super(robot);
}
/**
* 一遍扫地一边跳舞
*/
@Override
public void sweep() {
super.sweep();
dance();
}
/**
* 唱歌
*/
public void dance(){
System.out.println("跳一支探戈");
}
}
客户端调用:
public class DecoratorTest {
public static void main(String[] args) {
Robot robot = new ConcreatRobot();
RobotDecorator signRobot = new ConcreatDecoratorA(robot);
signRobot.sweep();
RobotDecorator danceRobot = new ConcreatDecoratorB(robot);
danceRobot.sweep();
}
}
控制台输出:
打扫卧室
唱一首单身情歌
打扫卧室
跳一支探戈
从例子中可以看出,装饰的模式是一种组合的概念,所谓装饰,就是要去扩展被装饰者从而扩展功能,而我们客户端在调用的时候,可以选择合适的装饰类来达到我们想要的结果。比如我只想让机器人扫地,那么我只需要调用具体的抽象实现类,我想让机器人又扫地又跳舞,那么就使用包装类。
装饰者模式是为已有功能动态的添加更多功能的一种方式。那么,在什么时候使用呢,当系统需要新的功能的时候,向旧代码中添加新的代码显然增加了原有代码的复杂度,并且违反了开闭原则。这个时候,装饰者提供了一个非常好的机会,我们可以通过包装这个主类,把每个要包装的功能单独的写成一个包装类,从而扩展该类的功能。客户端可以根据需要,调用特定功能的包装类。这样做的好处是把类中的装饰功能或者和核心功能分开,简化原有的类。
装饰者模式在java IO中的应用
装饰者模式在JDK中的典型应用莫过于IO体系了。Java中的IO分为两种,字节流和字符流,其中每一种分别可以分为输入流和输出流,我们来看下IO体系的结构图。

我们以InputStream为例,InputStream是抽象的父类,FilterInputtream和ByteArrayInputStream继承了InputStream,这些类继承了InputStream的基本的字节读取功能,FilterInputtream还持有了InputStream的引用,它的所有的方法,都是调用了这个引用的同名方法,也就是说,他把所有的调用都委托给了InputStream。并且FilterInputtream还有一些子类,比如BufferdInputStream,DataInputStream等。BufferedInputStream提供了缓冲输入流,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。由此可见,BufferedInputStream为我们提供了一个从缓冲区读取数据的功能,就像是科沃斯机器人,经过包装后为我们提供了唱歌的功能一样。我们来看下这几个类的类图。

正如图中所示,他们的结构完全满足装饰者模式的结构图。同理,像DataInputStream也是一个包装类,他提供了与机器无关方式从底层输入流中读取的功能。
我们来看下以BufferdInputStream实现数据读取的例子。
public class DecoratorTest {
public static void main(String[] args) {
InputStream in = null;
BufferedInputStream bi = null;
try {
in = new FileInputStream("E:\\decorator.txt");
//in 是一个文件输入流,通过BufferedInputStream给它添加从缓冲区读取数据的功能。
//BufferedInputStream就是一个包装类
bi = new BufferedInputStream(in);
int count = 0;
byte[] buffer = new byte[1024];
while ((count = bi.read(buffer)) != -1) {
System.out.println(new String(buffer, 0, count));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bi != null) {
bi.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
我自己之前学习IO流的时候,看到这么多种类的流也是很难理解,当我以设计模式角度去解读这些流时,会更加容易理解。装饰者模式的实现对于使用者是透明的,我们必须了解这个装饰者是什么功能,才能恰当的使用这个类。