Decorator Pattern(装饰器模式)
定义:Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.(动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活)
1.Component抽象构件:
Component是一个接口或者一个抽象类,就是定义我们最核心的对象,也就是最原始的对象,最高层次的抽象,统一整个装饰器系统,用于装饰器之间沟通的桥梁,就像II/O流中的InputStream,OutputStream一样
2.ConcreteComponent具体构件
ConcreteComponent是最核心,最原始,最基本的接口或者抽象类的实现,你要装饰的就是它,装饰的源头,你装饰的最底层,就像I/O流一样,这就直接跟底层打交道的节点流,就比如FileInputStream
3.Decorator 装饰角色
一般是一个抽象类,实现接口或者抽象方法,它并不一定有抽象方法,但在它的属性里必须有一个private变量指向Component抽象构件,一般是用构造器初始化。就像I/O流中的FilterInputStream
4.ConcreteDecoratorA,ConcreteDecoratorB具体的装饰器角色,这就是要将我们之间建的最核心,最原始,最基础的东西装饰成东西或者其他的东西,就像I/O中的BufferedInputStream,DataInputStream等。
案例:
Component
public interface Component {
void operator();
}
ConcreteComponent
public class ConcreteComponent implements Component {
@Override
public void operator() {
System.out.println("...ConcreteComponent,最原始的实现类,在最底层没有被装饰的");
}
}
Decorator
public abstract class Decorator implements Component{
Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operator() {
component.operator();
}
}
ConcreteDecoratorA
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
public void methodA() {
System.out.println("...ConcreteDecoratorA...添加的新功能");
}
@Override
public void operator() {
methodA();
super.operator();
System.out.println("...ConcreteDecoratorA的operator执行完毕");
}
}
ConcreteDecoratorB
public class ConcreteDecoratorB extends Decorator{
public ConcreteDecoratorB(Component component) {
super(component);
}
public void methodB() {
System.out.println("...ConcreteDecoratorB...添加的新功能");
}
@Override
public void operator() {
methodB();
super.operator();
System.out.println("...ConcreteDecoratorB的operator执行完毕");
}
}
运行结果:
...ConcreteComponent,最原始的实现类,在最底层没有被装饰的
...ConcreteDecoratorA...添加的新功能
...ConcreteDecoratorB...添加的新功能
...ConcreteComponent,最原始的实现类,在最底层没有被装饰的
...ConcreteDecoratorB的operator执行完毕
...ConcreteDecoratorA的operator执行完毕
优点:
1.相比与静态的继承,装饰器模式正如它定义的,那样可以动态的给一个对象添加额外的职责, 显得更加灵活。静态继承的情况下,如果要添加其他的功能就需要添加新的子类实现功能,然后相互之间继承,以达到一个组合的功能,对于每一个要添加的功能都要,新建类,显得特别麻烦,也使得系统越来越复杂,而对于装饰器来说,为一个特定的Component提供多种不同的Decorator,对于一些要达成的功能,相互组合就可以达成目的
2.装饰类和被装饰类可以独立发展,而不会相互耦合
3.装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component,实现的还是is-a的关系
缺点:
对于装饰模式记住一点就足够了:多层的装饰是比较复杂的。为什么会复杂呢?你想想看,就像剥洋葱一样,你剥到了最后才发现是最里层的装饰出现了问题,想象一下工作量吧,因此,尽量减少装饰类的数量,以便降低系统的复杂度
装饰模式在java.io包中广泛使用,包括基于字节流的InputStream/OutputStream和基于字符的Reader/Writer体系。以下以InputStream为例。
InputStream是所有字节输入流的基类,其下有众多子类,如基于文件的FileInputStream、基于对象的ObjectInputStream、基于字节数组的ByteArrayInputStream等。有些时候,我们想为这些流加一些其他的小特性,如缓冲、压缩等,用装饰模式实现就非常方便。相关的部分类图如下所示。
这个类图很标准,其中:
构件是InputStream;
构件实体是FileInputStream、ObjectInputStream等等;
装饰器是FilterInputStream;
装饰器实体是FilterInputStream的所有子类。
观察一下装饰器FilterInputStream的开头,可以发现它持有InputStream的引用,并且实现了InputStream中的所有方法(实际上就是简单地代理了一下)。具体的装饰器实体就继承FilterInputStream,并实现对应的扩充功能。如下图所示。
以下就可以用BufferedInputStream和GZIPInputStream创建一个带缓冲、压缩的文件输入流。
InputStream is = new GZIPInputStream(new BufferedInputStream(new FileInputStream("test.txt")));
InputStream类型中的装饰模式是半透明的
InputStream
类型中的装饰模式是半透明的。为了说明这一点,不妨看一看装饰模式的抽象构建角色InputStream
抽象类的源代码。这个抽象类声明了九个方法,并给出了其中八个的实现,另外一个是抽象方法,需要子类实现。
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
//……
}
public long skip(long n) throws IOException {
//……
}
public int available() throws IOException {
//……
}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public boolean markSupported() {
return false;
}
}
下面是作为装饰模式的抽象装饰角色FilterInputStream
类的源代码。可以看出,FilterInputStream
的接口与InputStream
的接口是完全一致的,也就是说,直到这一步,还是与装饰模式相吻合的。
public class FilterInputStream extends InputStream {
public int read() throws IOException {
return in.read();
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public long skip(long n) throws IOException {
return in.skip(n);
}
public int available() throws IOException {
return in.available();
}
public void close() throws IOException {
in.close();
}
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
public synchronized void reset() throws IOException {
in.reset();
}
public boolean markSupported() {
return in.markSupported();
}
}
下面是具体装饰角色PushbackInputStream
的源代码:
public class PushbackInputStream extends FilterInputStream {
private void ensureOpen() throws IOException {
if (in == null)
throw new IOException("Stream closed");
}
public int read() throws IOException {
//……
}
public int read(byte[] b, int off, int len) throws IOException {
//……
}
public void unread(int b) throws IOException {
//……
}
public void unread(byte[] b, int off, int len) throws IOException {
//……
}
public void unread(byte[] b) throws IOException {
unread(b, 0, b.length);
}
public int available() throws IOException {
//……
}
public long skip(long n) throws IOException {
//……
}
public boolean markSupported() {
return false;
}
public synchronized void mark(int readlimit) {
}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public synchronized void close() throws IOException {
//……
}
}
通过查看源代码,你会发现,这个装饰类提供了额外的方法unread()
,这就意味着PushbackInputStream
是一个半透明的装饰类。换句话说,它破坏了理想的装饰模式的要求。如果客户端持有一个类型为InputStream
对象的引用in
的话,那么如果in
的真实类型是PushbackInputStream
的话,只要客户端不需要使用unread()
方法,那么客户端一般没有问题。但是如果客户端必须使用这个方法,就必须进行向下类型转换。将in
的类型转换成为PushbackInputStream
之后才可能调用这个方法。但是,这个类型转换意味着客户端必须知道它拿到的引用是指向一个类型为PushbackInputStream
的对象。这就破坏了使用装饰模式的原始用意。
现实世界与理论总归是有一段差距的。纯粹的装饰模式在真实的系统中很难找到。一般所遇到的,都是这种半透明的装饰模式
示例程序
下面是使用I/O流读取文件内容的简单示例
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class InputStreamTest {
public static void main(String[] args) {
DataInputStream dis = null;
try {
dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("23.txt")
)
);
byte[] bytes = new byte[dis.available()];
dis.read(bytes);
String content = new String(bytes);
System.out.println(content);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
观察上面的代码,会发现最里层是一个FileInputStream
对象,然后把它传递给一个BufferedInputStream
对象,经过BufferedInputStream
对象处理后,再将处理后的对象传递给DataInputStream
对象进行处理。这个过程,其实就是装饰器的组装过程,FileInputStream
对象相当于原始的被装饰的对象,而BufferedInputStream
对象和DataInputStream
对象则相当于装饰器。
Flink State TTL中的装饰模式
为状态增加TTL的特性可以直接在原始状态之上实现,因此符合装饰模式的场景。Flink引入了一个AbstractTtlDecorator<T>抽象类作为装饰器,负责为状态类型T装饰上与TTL相关的基本逻辑。相关的部分类图如下所示。
可见,虽然AbstractTtlDecorator并未持有State的实例(只有State的类型参数),但是在其子类AbstractTtlState中,通过持有TTL状态上下文TTLStateContext间接地得到了State实例。例如,由AbstractTtlState派生出来的TtlMapState直接在原来的MapState上进行增删改查操作,只是附带上了AbstractTtlDecorator和AbstractTtlState提供的TTL逻辑而已。其他的TtlListState等也是同理