裝飾一時爽,一直裝飾一直爽

裝飾一時爽,一直裝飾一直爽

Decorator Pattern

裝飾模式解決的問題

如果使用繼承去建構所有功能,當這些功能需要各種排列組合時,會導致類別爆炸增加;裝飾模式是利用組合大於繼承的原則解決功能的擴充問題

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 {
    // 支援快取功能、對基本資料型別讀取
}

在繼承結構中去建構所有功能,可能會導致類別爆炸增加;要對一個類別有"強化功能"的需求時,應該考慮組合大於繼承的原則

裝飾器模式原理、實作

裝飾器模式包含四個部分

1. 抽象元件

抽象元件是最上層的父類別,所有的裝飾者、被裝飾者都繼承了抽象元件;這樣可以對具體元件嵌套多個具體裝飾者

public abstract class InputStream {

}

2. 具體元件

具體元件是原始的"被裝飾者",會繼承抽象元件;需求就是從對具體元件強化功能開始的

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();

3. 抽象裝飾者

引入抽象裝飾者的目的是,讓具體裝飾者類別的方法只需要實作要強化的方法就好

// 假設沒有抽象裝飾者,每個裝飾者都要委託所有方法
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();
    }
}

4. 裝飾者

裝飾者繼承抽象裝飾者,對原始功能相關的強化

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 的被裝飾者

總結

  • 裝飾者模式使用組合、委託來動態擴充功能,而非繼承
  • 裝飾者可以在被裝飾者的行為前或後,強化原始功能
  • 裝飾者、被裝飾者利用繼承,是達到"型態相同"的目的,而不是用來繼承相同的行為;行為是來自於基礎元件,與層層嵌套的裝飾者之間的組合關係
  • 過度使用裝飾者模式會產生很多小類別