裝飾模式

裝飾模式23種設計模式之一,英文名叫Decorator Pattern,又叫裝飾者模式

裝飾模式可以在不必改變原類文件和不使用繼承的情況下,動態的擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。通過使用裝飾模式,可以在運行時擴充一個類的功能。

原理是:增加一個裝飾類包裹原來的類,包裹的方式一般是通過在將原來的對象作爲裝飾類的構造函數的參數。裝飾類實現新的功能,但是,在不需要用到新功能的地方,它可以直接調用原來的類中的方法。裝飾類必須和原來的類有相同的接口。


裝飾模式是類繼承的另外一種選擇。

裝飾模式與類繼承的區別:類繼承在編譯時候增加行爲,而裝飾模式是在運行時增加行爲。

當有幾個相互獨立的功能需要擴充時,這個區別就變得很重要。在有些面向對象的編程語言中,類不能在運行時被創建,通常在設計的時候也不能預測到有哪幾種功能組合。這就意味著要爲每一種組合創建一個新類。相反,裝飾模式是面向運行時候的對象實例的,這樣就可以在運行時根據需要進行組合。一個裝飾模式的示例是JAVA裏的Java I/O的實現。


裝飾模式的UML如下:


裝飾模式共涉及4個角色:

抽象構建角色(Component):該角色用於抽象需要裝飾的對象,也就是原始對象。

具體構建對象(ConcreteComponent):該角色需要實現抽象構建角色的接口,是具體被裝飾的原始對象,有自己的原始邏輯實現。

裝飾角色(Decorator):該角色持有一個構建對象的實例,並定義一個與抽象構建角色一樣的接口。

具體裝飾角色(ConcreteDecorator):該角色用來裝飾構建角色,是我們需要添加的裝飾。


上述的角色代碼實現如下:

抽象構建角色Component

public interface Component {

	public void baseMethod();
	
}

具體構建角色ConcreteComponent

public class ConcreteComponent implements Component {

	@Override
	public void baseMethod() {
		// TODO Auto-generated method stub
		//sth to do...
		System.out.println("It is the basic action...");
	}
	
}

裝飾角色Decorator:

public class Decorator implements Component {

	private Component component;
	
	public Decorator(Component component) {
		this.component = component;
	}
	
	@Override
	public void baseMethod() {
		component.baseMethod();
	}
	
}

具體裝飾角色ConcreteDecorator

public class ConcreteDecorator extends Decorator {

	public ConcreteDecorator(Component component) {
		super(component);
	}
	
	private void decorateMethod() {
		//sth you want add on component
		System.out.println("Add something to decotate it...");
	}
	
	@Override
	public void baseMethod() {
		super.baseMethod();
		decorateMethod();
	}
	
}

以上就是裝飾模式的4種角色,客戶端調用的時候,只需要把構建角色(原始角色)作爲參數傳進裝飾角色中去

客戶調用:

public class Client {

	public static void main(String[] args) {
		Component component = new ConcreteComponent();
		component = new ConcreteDecorator(component);
		component.baseMethod();
	}
}

這些或許還看不出裝飾模式的一些優點,比如上述過程完全可以繼承來實現,而且過程還簡單。這裏再給出裝飾模式的一個具體應用


一個裝飾模式的具體應用

場景:現有一個蛋糕類,實際使用(吃)的時候,我們可能需要根據客戶的需求向蛋糕Cake上加上各種各樣的點心甜點(櫻桃、巧克力、奶酪...)進行裝飾,而且根據實際情況我們也不知道顧客的具體需求搭配,如果使用繼承就相當於做出各種搭配的蛋糕,如果只有(櫻桃、巧克力、奶酪)三種配料,那麼使用繼承產生的子類就可能有以下這些:

  • 櫻桃蛋糕
  • 巧克力蛋糕
  • 奶酪蛋糕
  • 櫻桃+巧克力蛋糕
  • 櫻桃+奶酪蛋糕
  • ...

根據組合,一共應該有7種搭配嗎,這還只是3種配料點心的情況下,如果按照實際中,幾十種點心,那麼產生的子類將是氾濫的。

因此我們考慮使用裝飾模式去解決,先只是做好構建對象(原始對象)蛋糕,在做好各種裝飾對象點心,然後客戶端調用的時候只需要自行添加裝飾就可以了。


具體的UML如下:



代碼實現如下:

抽象構建角色Cake,有一個description描述方法,可能的種類有甜蛋糕、鹹蛋糕、酸蛋糕...

public interface Cake {

	public void description();
	
}


具體的構建角色SweetCake:

public class SweetCake implements Cake {

	@Override
	public void description() {
		System.out.println("這是甜味蛋糕");
	}
	
}

當然這樣的甜蛋糕是不美味的,因爲只是只甜味麪包,我們需要加上各種奶油和水果

裝飾角色Dessert:

public class Dessert implements Cake {

	private Cake cake;
	
	public Dessert(Cake cake) {
		this.cake = cake;
	}
	
	@Override
	public void description() {
		cake.description();
	}
}

具體的裝飾角色,這裏給出了三種點心裝飾Cherry、Chocolate、Cheese

public class Cherry extends Dessert {

	public Cherry(Cake cake) {
		super(cake);
	}
	
	public void add() {
		System.out.println("已經加了【櫻桃】搭配");
	}
	
	@Override
	public void description() {
		super.description();
		add();
	}
	
}

public class Chocolate extends Dessert {

	public Chocolate(Cake cake) {
		super(cake);
	}
	
	public void add() {
		System.out.println("已經加了【巧克力】搭配");
	}
	
	@Override
	public void description() {
		super.description();
		add();
	}
	
}

public class Cheese extends Dessert {

	public Cheese(Cake cake) {
		super(cake);
	}
	
	public void add() {
		System.out.println("已經加了【奶酪】搭配");
	}
	
	@Override
	public void description() {
		super.description();
		add();
	}
	
}

以上使用裝飾模式的蛋糕製作就完成了

當客戶端調用時,可以根據需求自行搭配

public class Client {

	public static void main(String[] args) {
		Cake cake = new SweetCake();
		
		cake = new Chocolate(cake);
		cake = new Cherry(cake);
		cake = new Cheese(cake);
		
		cake.description();
	}
	
}

或者

public class Client {

	public static void main(String[] args) {
		Cake cake = new SweetCake();
		
		cake = new Chocolate(cake);
		cake = new Cheese(cake);
		
		cake.description();
	}
	
}

或者...

以上可以總結出裝飾模式的一些優點:

  • 裝飾類和被裝飾類可以獨立發展,而不會相互耦合。即Component類無須知道Decorator類,Decorator類是從外部來擴展Component類的功能,而Decorator類也不用直到具體的構建。
  • 裝飾模式是繼承關係的一種替代方案。裝飾類Decorator不管裝飾多少層,返回的對象還是Component,而且裝飾模式可以防止子類的泛濫。
  • 裝飾模式可以動態的擴展類的功能,而子類繼承只能是靜態的擴展類的功能

當然也有一些缺點:

  • 多層裝飾是比較複雜的
  • 會產生更多的的對象(可以發現上述客戶調用使用了大量的new)


一些使用裝飾模式的場景:

  • 需要擴展一個類的功能,或者給一個類增加附加功能;
  • 需要動態的給一個類添加功能,這些功能還可以動態的撤銷;
  • 需要爲一批類進行改裝或者加裝功能(比如,上述還可能有鹹蛋糕、酸蛋糕原始對象)


裝飾模式的一個典型使用就是Java中的輸入輸出流

這裏只列出輸入流來進行說明:


這裏

抽象構建角色(Component)是InputStream,查看源碼可以發現InputStream是一個抽象類,包含了核心抽象方法read(),沒有任何實現,它只是規範原始對象。

具體構建角色(Concrete Component):這裏是ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream,它們實現了InputStream定義的抽象方法read()

抽象裝飾角色(Decorator):這裏是FilterInputStream,它繼承了InputStream,含有一個InputStream類的實例,但其中的核心read方法沒有自己實現,而是直接引用了InputStream的read()方法。

具體裝飾角色(Concrete Decorator):這是是下排的BufferedInputStream等,它們繼承了FilterInputStream,實現了read()方法,創建BufferedInputStream時,需要把一個Component對象,也就是InputStream對象傳進它的構造方法。


我們通過源碼來驗證上述觀點:

InputStream只是一個抽象類,核心的read()方法沒有提高實現,只是定義了原始對象的規範,是原始對象的抽象,和我們的Cake類是相似的,只是我們的Cake應用是使用接口,差別不大。




具體構建角色ByteArrayInputStream相當於上述的SweetCake,是一個原始對象,但是實現了InputStream定義的抽象read()方法

/**
 * A <code>ByteArrayInputStream</code> contains
 * an internal buffer that contains bytes that
 * may be read from the stream. An internal
 * counter keeps track of the next byte to
 * be supplied by the <code>read</code> method.
 * <p>
 * Closing a <tt>ByteArrayInputStream</tt> has no effect. The methods in
 * this class can be called after the stream has been closed without
 * generating an <tt>IOException</tt>.
 *
 * @author  Arthur van Hoff
 * @see     java.io.StringBufferInputStream
 * @since   JDK1.0
 */
public
class ByteArrayInputStream extends InputStream {

...

 /**
     * Reads the next byte of data from this input stream. The value
     * byte is returned as an <code>int</code> in the range
     * <code>0</code> to <code>255</code>. If no byte is available
     * because the end of the stream has been reached, the value
     * <code>-1</code> is returned.
     * <p>
     * This <code>read</code> method
     * cannot block.
     *
     * @return  the next byte of data, or <code>-1</code> if the end of the
     *          stream has been reached.
     */
    public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }

    /**
     * Reads up to <code>len</code> bytes of data into an array of bytes
     * from this input stream.
     * If <code>pos</code> equals <code>count</code>,
     * then <code>-1</code> is returned to indicate
     * end of file. Otherwise, the  number <code>k</code>
     * of bytes read is equal to the smaller of
     * <code>len</code> and <code>count-pos</code>.
     * If <code>k</code> is positive, then bytes
     * <code>buf[pos]</code> through <code>buf[pos+k-1]</code>
     * are copied into <code>b[off]</code>  through
     * <code>b[off+k-1]</code> in the manner performed
     * by <code>System.arraycopy</code>. The
     * value <code>k</code> is added into <code>pos</code>
     * and <code>k</code> is returned.
     * <p>
     * This <code>read</code> method cannot block.
     *
     * @param   b     the buffer into which the data is read.
     * @param   off   the start offset in the destination array <code>b</code>
     * @param   len   the maximum number of bytes read.
     * @return  the total number of bytes read into the buffer, or
     *          <code>-1</code> if there is no more data because the end of
     *          the stream has been reached.
     * @exception  NullPointerException If <code>b</code> is <code>null</code>.
     * @exception  IndexOutOfBoundsException If <code>off</code> is negative,
     * <code>len</code> is negative, or <code>len</code> is greater than
     * <code>b.length - off</code>
     */
    public synchronized int read(byte b[], int off, int len) {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        }

        if (pos >= count) {
            return -1;
        }

        int avail = count - pos;
        if (len > avail) {
            len = avail;
        }
        if (len <= 0) {
            return 0;
        }
        System.arraycopy(buf, pos, b, off, len);
        pos += len;
        return len;
    }

再看同樣繼承InputStream的FilterInputStream對象,它是一個裝飾角色,相當於上述的點心類(Dessert),包含一個Component類的引用,沒有實現read()方法,而是直接調用原始類的方法。

/**
 * 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   JDK1.0
 */
public
class FilterInputStream extends InputStream {
    /**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;  //包含Component的一個實例

    /**
     * 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;
    }

    /**
     * Reads the next byte of data from this input stream. The value
     * byte is returned as an <code>int</code> in the range
     * <code>0</code> to <code>255</code>. If no byte is available
     * because the end of the stream has been reached, the value
     * <code>-1</code> is returned. This method blocks until input data
     * is available, the end of the stream is detected, or an exception
     * is thrown.
     * <p>
     * This method
     * simply performs <code>in.read()</code> and returns the result.
     *
     * @return     the next byte of data, or <code>-1</code> if the end of the
     *             stream is reached.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FilterInputStream#in
     */
    public int read() throws IOException {  //沒有自己實現read()方法,直接調用Component的原始方法,可以比較Dessert類
        return in.read();
    }

再看具體的裝飾角色BufferedInputStream等,它實現了抽象裝飾角色,需要在構造方法中傳一個原始對象給抽象裝飾角色,相當於上述Cherry等裝飾點心,可以類比之。

public
class BufferedInputStream extends FilterInputStream {

...

 /**
     * Creates a <code>BufferedInputStream</code>
     * with the specified buffer size,
     * and saves its  argument, the input stream
     * <code>in</code>, for later use.  An internal
     * buffer array of length  <code>size</code>
     * is created and stored in <code>buf</code>.
     *
     * @param   in     the underlying input stream.
     * @param   size   the buffer size.
     * @exception IllegalArgumentException if size <= 0.
     */
    public BufferedInputStream(InputStream in, int size) {
        super(in); //傳原始對象給父類抽象裝飾角色
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
實現了read()方法

 /**
     * See
     * the general contract of the <code>read</code>
     * method of <code>InputStream</code>.
     *
     * @return     the next byte of data, or <code>-1</code> if the end of the
     *             stream is reached.
     * @exception  IOException  if this input stream has been closed by
     *                          invoking its {@link #close()} method,
     *                          or an I/O error occurs.
     * @see        java.io.FilterInputStream#in
     */
    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }

其他的輸入流類也是一樣,可以參照源碼,類比之前的Cake蛋糕應用。

發佈了65 篇原創文章 · 獲贊 34 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章