裝飾模式
裝飾模式
一、概念
裝飾模式(Decorator Pattern):動態地給一個物件增加一些額外的職責,就增加物件功能來說,裝飾模式比生成子類實現更為靈活。裝飾模式是一種物件結構型模式。
定義一個抽象的裝飾類,將具體的裝飾類作為其子類,然後繼承具體的裝飾類。
二、使用場景
- 在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。
- 當不能採用繼承的方式對系統進行擴充套件或者採用繼承不利於系統擴充套件和維護時可以使用裝飾模式。
使用裝飾模式的步驟:
- 在裝飾類的內部維護一個被裝飾類的引用。
- 讓裝飾類有一個共同的父類或者是父介面。
三、UML結構圖

裝飾模式UML.png
四、程式碼示例
案例一:
Component:
public interface Component { public void operation(); }
Decorator:
public class Decorator implements Component{ //維持一個對抽象構件物件的引用 private Component component; //注入一個抽象構件型別的物件(通過構造方法或者set方法) public Decorator(Component component) { this.component = component; } @Override public void operation() { //呼叫原有業務方法,此處沒有真正的實現operation方法,具體的裝飾過程交由子類完成 component.operation(); } }
ConcreateDecorator:
public class ConcreateDecorator extends Decorator{ public ConcreateDecorator(Component component) { super(component); } public void operation(){ //呼叫原有業務方法 super.operation(); //呼叫新增業務方法 addedBehavior(); } //新增業務方法 public void addedBehavior(){ } }
案例二:
使用繼承的方式增強一個類的功能:
package com.hcx.pattern; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.Reader; /** * 拓展BufferedReader的功能, 增強readLine方法,返回的字串帶有行號。 * @author hcx * */ class BufferedLineNum extends BufferedReader{ //行號 int count = 1; public BufferedLineNum(Reader in) { super(in); } @Override public String readLine() throws IOException { String line = super.readLine(); if(line==null) { return null; } line = count+" "+line; count++; return line; } } /** * 帶分號的緩衝輸入字元流 * @author hcx * */ class BufferedSemi extends BufferedReader{ public BufferedSemi(Reader in) { super(in); } @Override public String readLine() throws IOException { String line = super.readLine(); if(line==null) { return null; } line = line+";"; return line; } } /** * 帶雙引號的緩衝輸入字元流 * @author hcx * */ class BufferedQuto extends BufferedReader{ public BufferedQuto(Reader in) { super(in); } @Override public String readLine() throws IOException { String line = super.readLine(); if(line == null) { return null; } line = "\""+line+"\""; return line; } } public class Demo1 { public static void main(String[] args) throws IOException { File file = new File("F:\\Demo1.java"); //建立資料的輸入通道 FileReader fileReader = new FileReader(file); //建立帶行號的緩衝輸入字元流 BufferedLineNum bufferedLineNum = new BufferedLineNum(fileReader); //帶有分號的緩衝輸入字元流 BufferedSemi bufferedSemi = new BufferedSemi(fileReader); //帶有雙引號的緩衝輸入字元流 BufferedQuto bufferedQuto = new BufferedQuto(fileReader); String line = null; while((line = bufferedLineNum.readLine())!=null) { System.out.println(line); } } }
使用裝飾模式增強一個類的功能:
package com.hcx.pattern; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; /** * 帶行號的緩衝輸入字元流 * @author hcx * */ class BufferedLineNum2 extends BufferedReader{ //在內部維護一個被裝飾類的引用。 BufferedReader bufferedReader; int count = 1; public BufferedLineNum2(BufferedReader bufferedReader) { // 注意: 該語句沒有任何的作用,只不過是為了讓程式碼不報錯。 super(bufferedReader); this.bufferedReader = bufferedReader; } public String readLine() throws IOException { String line = bufferedReader.readLine(); if(line==null) { return null; } line = count+" "+line; count++; return line; } } /** * 帶分號緩衝輸入字元流 * 繼承的原因:為了讓這些裝飾類的物件可以作為引數進行傳遞,達到互相裝飾的效果。 * @author hcx * */ class BufferedSemi2 extends BufferedReader{ //在內部維護一個被裝飾類的引用。 BufferedReader bufferedReader; public BufferedSemi2(BufferedReader bufferedReader) { //BufferReader沒有無參的構造方法,繼承了BufferedReader,所以要指定呼叫父類的帶參的構造方法 super(bufferedReader);// 注意: 該語句沒有任何的作用,只不過是為了讓程式碼不報錯。 this.bufferedReader = bufferedReader; } public String readLine() throws IOException{ //建立該類物件時,如果傳入的是buffereLineNum,則這裡的ReadLine方法是呼叫了buffereLineNum的readLine方法. String line = bufferedReader.readLine(); if(line==null){ return null; } line = line +";"; return line; } } /** * 帶雙引號緩衝輸入字元流 * @author hcx * */ class BufferedQuto2 extends BufferedReader{ //在內部維護一個被裝飾的類 BufferedReader bufferedReader; public BufferedQuto2(BufferedReader bufferedReader){//newBufferedSemi2(); super(bufferedReader) ; //只是為了讓程式碼不報錯 this.bufferedReader = bufferedReader; } public String readLine() throws IOException{ //建立該類物件時,如果傳入的是bufferedSemi2,則這裡的ReadLine方法是呼叫了bufferedSemi2的readLine方法. String line = bufferedReader.readLine(); if(line==null){ return null; } line = "\""+line +"\""; return line; } } public class Demo2 { public static void main(String[] args) throws IOException { File file = new File("F:\\Demo1.java"); FileReader fileReader = new FileReader(file); //建立緩衝輸入字元流 BufferedReader bufferedReader = new BufferedReader(fileReader); //建立帶行號的緩衝輸入字元流 BufferedLineNum2 bufferedLineNum = new BufferedLineNum2(bufferedReader); //帶分號的緩衝輸入字元流 BufferedSemi2 bufferedSemi2 = new BufferedSemi2(bufferedLineNum); //帶雙引號的緩衝輸入字元流 BufferedQuto2 bufferedQuto2 = newBufferedQuto2(bufferedSemi2); String line = null; while((line = bufferedQuto2.readLine())!=null){ System.out.println(line); } } }
案例三:
一家三口每個人都會工作,兒子的工作就是畫畫,母親的工作就是在兒子的基礎上做一個增強,不單止可以畫畫,還可以上塗料。爸爸的工作就是在媽媽基礎上做了增強,就是上畫框。
interface Work{ public void work(); } class Son implements Work{ @Override public void work() { System.out.println("畫畫"); } } class Mather implements Work{ //需要被增強的類。 Work worker; public Mather(Work worker){ this.worker = worker; } @Override public void work() { worker.work(); System.out.println("給畫上顏色"); } } class Father implements Work{ //需要被增強的類的引用 Work worker; public Father(Work worker){ this.worker = worker; } @Override public void work() { worker.work(); System.out.println("上畫框"); } } public class Demo { public static void main(String[] args) { Son s = new Son(); //s.work(); Mather m = new Mather(s); //m.work(); Father f = new Father(s); f.work(); } }
總結:
繼承實現的增強類和裝飾模式實現的增強類有何區別?
繼承實現的增強類:
- 優點:程式碼結構清晰,而且實現簡單.
- 缺點:對於每一個的需要增強的類都要建立具體的子類來幫助其增強,這樣會導致繼承體系過於龐大。
裝飾模式實現的增強類:
- 優點:內部可以通過多型技術對多個需要增強的類進行增強,可以使這些裝飾類達到互相裝飾的效果。使用比較靈活。
- 缺點:需要內部通過多型技術維護需要被增強的類的例項。進而使得程式碼稍微複雜。
五、裝飾模式的優點
- 對於擴充套件一個物件的功能,裝飾模式比繼承更加靈活,不會導致類的個數急劇增長。
- 可以通過一種動態的方式來擴充套件一個物件的功能
- 可以對一個物件進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合。