裝飾器模式與Java IO

裝飾器模式

裝飾器模式是一種類繼承的替代方案,用於以客戶端透明的方式在基類上增加各種新的功能。相比於繼承,使用裝飾器模式可以顯著減少類的個數。
以下以繪圖形狀爲例展示裝飾器模式。裝飾器模式的基礎是形狀接口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類,實現了具體的帶緩衝區的字節流輸入功能,相當於上面例子中的RedColorDecoratorBlueBorderDecorator類。

/**
 * 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是字節流輸入基類,ByteArrayInputStreamFileInputStream繼承InputStream,分別實現從字節數組的輸入和文件的輸入。FilterInputStream也繼承InputStream,同時包含InputStream對象,作爲裝飾器基類。BufferedInputStream, DataInputStream, PushBackInputStream繼承FilterInputStream,分別提供緩衝區、解析到Java基本類型和回寫到流的裝飾功能。因此,我們在寫緩衝區的文件字節流讀取時,經常採用下面的語句:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filename));
發佈了715 篇原創文章 · 獲贊 141 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章