Permalink裝飾模式解決的問題
如果使用繼承去建構所有功能,當這些功能需要各種排列組合時,會導致類別爆炸增加;裝飾模式是利用組合大於繼承的原則解決功能的擴充問題
public abstract class InputStream {
// ...
}
public class FileInputStream extends InputStream {
// 用來讀取文件
}
public class PipedInputStream extends InputStream {
// ...
}
InputStream bin = new FileInputStream("/test.txt");
byte[] data = new byte[128];
while (bin.read(data) != -1) {
// ...
}
如果需要擴充快取功能、對基本資料型別讀取,二種強化功能,使用繼承來擴充功能,會因為不同功能的排列組合,造成繼承結構複雜
public class BufferedFileInputStream extends FileInputStream {
// 支援快取功能
}
public class DataFileInputStream extends FileInputStream {
// 支援對基本資料型別讀取
}
public class BufferedDataFileInputStream extends FileInputStream {
// 支援快取功能、對基本資料型別讀取
}
public class BufferedPipedStream extends PipedInputStream {
// 支援快取功能
}
public class DataPipedInputStream extends PipedInputStream {
// 支援對基本資料型別讀取
}
public class BufferedDataPipedInputStream extends PipedInputStream {
// 支援快取功能、對基本資料型別讀取
}
在繼承結構中去建構所有功能,可能會導致類別爆炸增加;要對一個類別有"強化功能"的需求時,應該考慮組合大於繼承的原則
Permalink裝飾器模式原理、實作
裝飾器模式包含四個部分
Permalink1. 抽象元件
抽象元件是最上層的父類別,所有的裝飾者、被裝飾者都繼承了抽象元件;這樣可以對具體元件嵌套多個具體裝飾者
public abstract class InputStream {
}
Permalink2. 具體元件
具體元件是原始的"被裝飾者",會繼承抽象元件;需求就是從對具體元件強化功能開始的
public class DataInputStream extends InputStream {
protected volatile InputStream in;
protected DataInputStream(InputStream in) {
this.in = in
}
}
擴充 FileInputStream 功能
InputStream in = new FileInputStream("/test.txt");
DataInputStream din = new DataInputStream(in);
int data = din.readInt();
Permalink3. 抽象裝飾者
引入抽象裝飾者的目的是,讓具體裝飾者類別的方法只需要實作要強化的方法就好
// 假設沒有抽象裝飾者,每個裝飾者都要委託所有方法
public class BufferedInputStream extends InputStream {
protected volatile InputStream in;
protected BufferedInputStream(InputStream in) {
this.in = in;
}
// 傳進來的物件可能有對 f() 強化,所以不能單純繼承父類的方法
// 因此就算本類別沒有強化 f(),但還是要呼叫 in.f(),保留嵌套時傳進來的物件的方法
public void f() {
in.f();
}
}
抽象裝飾者,預設實作傳入物件的所有方法
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
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[], int off, int len) throws IOException {
return in.read(b, off, len);
}
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();
}
}
Permalink4. 裝飾者
裝飾者繼承抽象裝飾者,對原始功能相關的強化
public class BufferedInputStream extends FilterInputStream {
protected volatile InputStream in;
protected BufferedInputStream(InputStream in) {
this.in = in;
}
public void f() {
// 強化
in.f();
// 強化
}
}
同時支援快取、對基本資料型別讀取二種功能,對具體元件嵌套
InputStream in = new FileInputStream("/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();
不過裝飾者、被裝飾者是相對的,A 裝飾 B,B 裝飾 C A(B(C))
,B 是 C 的裝飾者,B 是 A 的被裝飾者
Permalink總結
- 裝飾者模式使用組合、委託來動態擴充功能,而非繼承
- 裝飾者可以在被裝飾者的行為前或後,強化原始功能
- 裝飾者、被裝飾者利用繼承,是達到"型態相同"的目的,而不是用來繼承相同的行為;行為是來自於基礎元件,與層層嵌套的裝飾者之間的組合關係
- 過度使用裝飾者模式會產生很多小類別