裝飾器模式
裝飾器模式是一種類繼承的替代方案,用於以客戶端透明的方式在基類上增加各種新的功能。相比於繼承,使用裝飾器模式可以顯著減少類的個數。
以下以繪圖形狀爲例展示裝飾器模式。裝飾器模式的基礎是形狀接口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));