java設計模式之裝飾器模式

負重前行的小牛發表於2020-11-15

裝飾器模式的定義:

  裝飾器模式也叫作包裝器模式,指在不改變原有物件的基礎上,動態地給一個物件新增一些額外的職責。就增加功能來說,裝飾器模式相比生成子類更為靈活,屬於結構性設計模式。

裝飾器模式提供了比繼承更有彈性的替代方案(擴充套件原有物件的功能)將功能附加到物件上,因此裝飾器模式的核心是擴充套件功能,使用裝飾器模式可以透明且動態地擴充套件類地功能。

裝飾器模式地應用場景:

  • 用於擴充套件一個類地功能,或者給一個類新增職責。
  • 動態地給一個物件新增功能,這些功能可以再動態地撤銷。
  • 需要為一批平行的兄弟類進行改裝或加裝功能。

裝飾器模式的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實現客戶端請求日誌的記錄。

裝飾器模式的優點:

  • 裝飾器是繼承的有力補充,比繼承靈活,在不改變原物件的情況下,動態地給一個物件擴充套件功能,即插即用。
  • 通過使用不同裝飾類及這些裝飾類的排列組合,可以實現不同效果。
  • 裝飾器模式完全遵守開閉原則。

裝飾器模式的缺點:

  • 會出現更多的程式碼、更多的類,增加程式的複雜性。
  • 動態裝飾在多層裝飾時會更復雜。

 

相關文章