装饰器模式
装饰器模式是一种类继承的替代方案,用于以客户端透明的方式在基类上增加各种新的功能。相比于继承,使用装饰器模式可以显著减少类的个数。
以下以绘图形状为例展示装饰器模式。装饰器模式的基础是形状接口Shape
.
public interface Shape {
public void draw();
}
核心是实现了Shape
接口并以Shape
对象作为属性的ShapeDecorator
装饰器基类。装饰器模式的关键就在于装饰器基类同时包含接口对象和继承接口。(可以思考为什么装饰器基类要同时使用包含和继承)
public class ShapeDecorator implements Shape {
protected Shape shape;
public ShapeDecorator(Shape shape) {
this.shape = shape;
}
@Override
public void draw() {
shape.draw();
}
}
假设我们用装饰器模式来装饰一个矩形形状Rectangle
。
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Rectangle");
}
}
分别使用红色填充装饰器RedColorDecorator
public class RedColorDecorator extends ShapeDecorator {
public RedColorDecorator(Shape shape) {
super(shape);
}
private void setRedColor() {
System.out.println("Color == Red");
}
@Override
public void draw() {
super.draw();
setRedColor();
}
}
和蓝色边框装饰器BlueBorderDecorator
public class BlueBorderDecorator extends ShapeDecorator {
public BlueBorderDecorator(Shape shape) {
super(shape);
}
private void setBlueBorder() {
System.out.println("Border == Blue");
}
@Override
public void draw() {
super.draw();
setBlueBorder();
}
}
最后,我们在客户端(驱动类Main
)中装饰矩形的填充和边框。
public class Main {
public static void main(String[] args) {
Shape shape = new Rectangle();
shape.draw();
System.out.println("--------------------");
shape = new RedColorDecorator(shape);
shape.draw();
System.out.println("--------------------");
shape = new BlueBorderDecorator(shape);
shape.draw();
System.out.println("--------------------");
// stdout
// Rectangle
// --------------------
// Rectangle
// Color == Red
// --------------------
// Rectangle
// Color == Red
// Border == Blue
// --------------------
}
}
如果我们要使用很多形状、很多填充、很多边框,那么,相比继承,使用装饰器模式可以大大减少类的个数。例如,我们使用矩形、圆形、三角形这3种形状,使用红、黄、蓝、绿这4种颜色的填充和边框,则使用继承的方式,除了基类外,要创建3*4*4=48
个具体的类,类的数量面临组合爆炸问题;而使用装饰器模式,除了基类外,只需要创建3+4+4=11
个具体的类(3个形状类,4个填充类,4个边框类),类的个数从乘法变成了加法,而且形状类、填充类、边框类各自的行为被抽离出来,便于统一管理。
java.io中的装饰器模式
java.io
包中大量使用装饰器模式。下面以字节输入流为例,主要涉及InputStream
, FilterInputStream
, FileInputStream
, BufferedInputStream
类。 jdk版本11.0.4.
InputStream
是抽象基类,相当于上面例子中的Shape
接口。
/**
* This abstract class is the superclass of all classes representing
* an input stream of bytes.
*
* <p> Applications that need to define a subclass of <code>InputStream</code>
* must always provide a method that returns the next byte of input.
*
* @author Arthur van Hoff
* @see java.io.BufferedInputStream
* @see java.io.ByteArrayInputStream
* @see java.io.DataInputStream
* @see java.io.FilterInputStream
* @see java.io.InputStream#read()
* @see java.io.OutputStream
* @see java.io.PushbackInputStream
* @since 1.0
*/
public abstract class InputStream implements Closeable
FileInputStream
类是InputStream
类的针对文件输入的一个具体实现,相当于上面的Rectangle
类。
/**
* A <code>FileInputStream</code> obtains input bytes
* from a file in a file system. What files
* are available depends on the host environment.
*
* <p><code>FileInputStream</code> is meant for reading streams of raw bytes
* such as image data. For reading streams of characters, consider using
* <code>FileReader</code>.
*
* @apiNote
* To release resources used by this stream {@link #close} should be called
* directly or by try-with-resources. Subclasses are responsible for the cleanup
* of resources acquired by the subclass.
* Subclasses that override {@link #finalize} in order to perform cleanup
* should be modified to use alternative cleanup mechanisms such as
* {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method.
*
* @implSpec
* If this FileInputStream has been subclassed and the {@link #close}
* method has been overridden, the {@link #close} method will be
* called when the FileInputStream is unreachable.
* Otherwise, it is implementation specific how the resource cleanup described in
* {@link #close} is performed.
*
* @author Arthur van Hoff
* @see java.io.File
* @see java.io.FileDescriptor
* @see java.io.FileOutputStream
* @see java.nio.file.Files#newInputStream
* @since 1.0
*/
public class FileInputStream extends InputStream
FilterInputStream
相当于上面例子中的ShapeDecorator
类,是装饰器基类,继承了InputStream
且包含InputStream
对象。
/**
* A <code>FilterInputStream</code> contains
* some other input stream, which it uses as
* its basic source of data, possibly transforming
* the data along the way or providing additional
* functionality. The class <code>FilterInputStream</code>
* itself simply overrides all methods of
* <code>InputStream</code> with versions that
* pass all requests to the contained input
* stream. Subclasses of <code>FilterInputStream</code>
* may further override some of these methods
* and may also provide additional methods
* and fields.
*
* @author Jonathan Payne
* @since 1.0
*/
public
class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
protected FilterInputStream(InputStream in) {
this.in = in;
}
...
}
BufferedInputStream
类进一步继承了FilterInputStream
类,实现了具体的带缓冲区的字节流输入功能,相当于上面例子中的RedColorDecorator
或BlueBorderDecorator
类。
/**
* A <code>BufferedInputStream</code> adds
* functionality to another input stream-namely,
* the ability to buffer the input and to
* support the <code>mark</code> and <code>reset</code>
* methods. When the <code>BufferedInputStream</code>
* is created, an internal buffer array is
* created. As bytes from the stream are read
* or skipped, the internal buffer is refilled
* as necessary from the contained input stream,
* many bytes at a time. The <code>mark</code>
* operation remembers a point in the input
* stream and the <code>reset</code> operation
* causes all the bytes read since the most
* recent <code>mark</code> operation to be
* reread before new bytes are taken from
* the contained input stream.
*
* @author Arthur van Hoff
* @since 1.0
*/
public
class BufferedInputStream extends FilterInputStream
最终形成的继承树中,InputStream
是字节流输入基类,ByteArrayInputStream
和FileInputStream
继承InputStream
,分别实现从字节数组的输入和文件的输入。FilterInputStream
也继承InputStream
,同时包含InputStream
对象,作为装饰器基类。BufferedInputStream
, DataInputStream
, PushBackInputStream
继承FilterInputStream
,分别提供缓冲区、解析到Java基本类型和回写到流的装饰功能。因此,我们在写缓冲区的文件字节流读取时,经常采用下面的语句:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filename));