裝飾器模式的定義:
裝飾器模式也叫作包裝器模式,指在不改變原有對象的基礎上,動態地給一個對象添加一些額外的職責。就增加功能來說,裝飾器模式相比生成子類更為靈活,屬於結構性設計模式。
裝飾器模式提供了比繼承更有彈性的替代方案(擴展原有對象的功能)將功能附加到對象上,因此裝飾器模式的核心是擴展功能,使用裝飾器模式可以透明且動態地擴展類地功能。
裝飾器模式地應用場景:
- 用於擴展一個類地功能,或者給一個類添加職責。
- 動態地給一個對象添加功能,這些功能可以再動態地撤銷。
- 需要為一批平行的兄弟類進行改裝或加裝功能。
裝飾器模式的UML類圖:
由上圖可以看到,裝飾器模式主要包含以下4個角色:
- 抽象組件(Component):可以是一個接口或者抽象類,充當被裝飾類的原始對象,規定了被裝飾對象的行為。
- 具體組件(ConcreteComponent):實現/繼承Component的一個具體對象,即被裝飾對象。
- 抽象裝飾器(Decorator):通用的裝飾concreteComponent的裝飾器,其內部必然有一個屬性指向Component;其實現一般是一個抽象類,主要是為了讓其子類按照其構造形式傳入一個Component,這是強制的通用行為。如果系統中裝飾邏輯單一,則並不需要實現許多的裝飾器,可以直接省略該類,而直接實現一個具體的裝飾器即可。
- 具體裝飾器(ConcreteDecorator):Decorator的具體實現類,理論上,每個ConcreteDecorator都擴展了Component對象的一種功能。
裝飾器模式的通用寫法:
package com.liuyi.designmode.structure.decorator;
import javax.swing.*;
public class Client {
public static void main(String[] args){
Component c1 = new ConcreteComponent (); //首先創建需要被裝飾的原始對象(即要被裝飾的對象)
Decorator decoratorA = new ConcreteDecoratorA(c1); //給對象透明的增加功能A並調用
decoratorA .operation();
Decorator decoratorB = new ConcreteDecoratorB(c1); //給對象透明的增加功能B並調用
decoratorB .operation();
Decorator decoratorBandA = new ConcreteDecoratorB(decoratorA);//裝飾器也可以裝飾具體的裝飾對象,此時相當於給對象在增加A的功能基礎上在添加功能B
decoratorBandA.operation();
}
//抽象組件
static interface Component {
/**
* 示例方法
*/
public void operation();
}
//具體組件
static class ConcreteComponent implements Component {
public void operation() {
//相應的功能處理
System.out.println("處理業務邏輯");
}
}
static abstract class Decorator implements Component {
/**
* 持有組件對象
*/
protected Component component;
/**
* 構造方法,傳入組件對象
* @param component 組件對象
*/
public Decorator(Component component) {
this.component = component;
}
public void operation() {
//轉發請求給組件對象,可以在轉發前后執行一些附加動作
component.operation();
}
}
//具體裝飾器A
static class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
private void operationFirst(){ } //在調用父類的operation方法之前需要執行的操作
private void operationLast(){ } //在調用父類的operation方法之后需要執行的操作
public void operation() {
//調用父類的方法,可以在調用前后執行一些附加動作
operationFirst(); //添加的功能
super.operation(); //這里可以選擇性的調用父類的方法,如果不調用則相當於完全改寫了方法,實現了新的功能
operationLast(); //添加的功能
}
}
//具體裝飾器B
static class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
private void operationFirst(){ } //在調用父類的operation方法之前需要執行的操作
private void operationLast(){ } //在調用父類的operation方法之后需要執行的操作
public void operation() {
//調用父類的方法,可以在調用前后執行一些附加動作
operationFirst(); //添加的功能
super.operation(); //這里可以選擇性的調用父類的方法,如果不調用則相當於完全改寫了方法,實現了新的功能
operationLast(); //添加的功能
}
}
}
分析裝飾器模式在IO流中的使用:
java中的IO流主要分為字符流和字節流,這里我們以常用的字符流的讀(Reader)為例來分析。首先我覺得要理解裝飾器模式在IO中的使用,首先我們必須要
明白怎么去區分哪些是裝飾類,哪些是被裝飾類。我們看上面的例子,發現具體的裝飾類ConcreteDecoratorA和ConcreteDecoratorB的構造函數的參數都是頂層接
口的實例對象。我們先通過JDK API文檔查看Reader接口有哪些實現類,然后我們根據這個規則去判斷哪些實現類是裝飾類,哪些實現類是被裝飾類。
我們先來查看BufferedReader的構造方法,發現傳入的參數是頂層抽象的實例對象,所以可以判定BufferedReader是一個裝飾類,並且它不是抽象的,所以這里直接忽略
了抽象裝飾器,直接實現了具體的裝飾器。當然Reader也有抽象的裝飾器,比如FilterReader,感興趣的同學可以自己去看API,但是它的具體的裝飾器實現很少用,當然
如果需要自定義Reader裝飾器,可以去繼承這個抽象裝飾器實現。
我們再來看InputStreamReader的構造方法,發現傳入的參數不是頂層抽象的實例對象,所以可以判斷InputStreamReader是一個被裝飾的類。
這里只列舉這兩個常用的被裝飾類和裝飾的類,目的只是為了讓大家理解裝飾器模式在IO流中是怎么運用的,來看看具體的使用,我們讀取項目路徑下的
名字為xiaoniu.txt文件里面的內容,然后打印出來,先來看不使用裝飾類即用InputStreamReader的情況下怎么使用:
package com.liuyi.designmode.structure.decorator; import java.io.FileInputStream; import java.io.FileReader; import java.io.InputStreamReader; /** * @ClassName IoTest * @description: * @author:liuyi * @Date:2020/11/14 23:01 */ public class IOTest { public static void main(String[] args) throws Exception { //讀取當前項目下的xiaoniu.txt FileInputStream fileInputStream = new FileInputStream("xiaoniu.txt"); InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream); //設置一次讀取1024個字節 char[] chars = new char[1024]; int index; while ((index=inputStreamReader.read(chars))!=-1){ System.out.println(String.valueOf(chars)); } fileInputStream.close(); inputStreamReader.close(); } }
先不着急使用裝飾類,我們發現使用的時候還要傳入一個InputStream的實例對象,通過查看JDK文檔,我們發現InputStreamReader還有一個子類FileReader,
看看FileReader怎么使用:
package com.liuyi.designmode.structure.decorator; import java.io.FileReader; /** * @ClassName IoTest * @description: * @author:liuyi * @Date:2020/11/14 23:01 */ public class IOTest { public static void main(String[] args) throws Exception { //讀取當前項目下的xiaoniu.txt FileReader fileReader = new FileReader("xiaoniu.txt"); //設置一次讀取1024個字節 char[] chars = new char[1024]; int index; while ((index=fileReader.read(chars))!=-1){ System.out.println(String.valueOf(chars)); } fileReader.close(); } }
FileReader也起到增強的作用,只不過是通過子類的方式實現的,只是這種方式不符合開閉原則,如果需要添加功能必須要修改子類,還要在接口添加抽象方法。
我們通常會讀取文件里面的內容的時候,需要一行一行的讀取,如果只用被裝飾類的方法是很難實現的,這個時候我們的裝飾器BufferedReader就閃亮登場了。
package com.liuyi.designmode.structure.decorator; import java.io.BufferedReader; import java.io.FileReader; /** * @ClassName IoTest * @description: * @author:liuyi * @Date:2020/11/14 23:01 */ public class IOTest { public static void main(String[] args) throws Exception { //讀取當前項目下的xiaoniu.txt FileReader fileReader = new FileReader("xiaoniu.txt"); BufferedReader bufferedReader = new BufferedReader(fileReader); //設置一次讀取1024個字節 String readLine = bufferedReader.readLine(); System.out.println(readLine); } }
裝飾器模式與代理模式的區別:
從代理模式UML類圖和通用代碼實現來看,代理模式與裝飾器模式幾乎一模一樣。從代碼實現來看,代理模式確實與裝飾器模式也是一樣的,但是這兩種設計模式
所面向的功能擴展面是不一樣的。
裝飾器模式強調自身的功能擴展,着重類功能的變化,比如添加方法。而代理模式強調對代理過程的控制,主要是對已有的方法進行功能增強,比如spring中通過
AOP實現客戶端請求日志的記錄。
裝飾器模式的優點:
- 裝飾器是繼承的有力補充,比繼承靈活,在不改變原對象的情況下,動態地給一個對象擴展功能,即插即用。
- 通過使用不同裝飾類及這些裝飾類的排列組合,可以實現不同效果。
- 裝飾器模式完全遵守開閉原則。
裝飾器模式的缺點:
- 會出現更多的代碼、更多的類,增加程序的復雜性。
- 動態裝飾在多層裝飾時會更復雜。