【設計模式】【結構型模式】裝飾器模式

概念

定義

裝飾模式指動態地給一個對象添加一些額外的職責,就增長功能來講,裝飾模式比生成子類更爲靈活。html

這種類型的設計模式屬於結構型模式,它是做爲現有的類的一個包裝。java

這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。web

通常的,咱們爲了擴展一個類常用繼承方式實現,因爲繼承爲類引入靜態特徵,而且隨着擴展功能的增多,子類會很膨脹。在不想增長不少子類的狀況下擴展類時可使用裝飾器模式。數據庫

對象加強的經常使用方式

不少時候咱們可能對Java提供給咱們的對象不滿意,不能知足咱們的功能。此時咱們就想對Java原對象進行加強,可以實現咱們想要的功能就好~設計模式

通常來講,實現對象加強有三種方式:服務器

一、繼 承:繼承父類,子類擴展
二、裝飾器模式:使用「包裝」的方式來加強對象
三、代理模式:靜態代理和動態代理app

繼承ide

最簡單的方式就是繼承父類,子類擴展來達到目的。雖然簡單,可是這種方式的缺陷很是大:函數

一、若是父類是帶有數據、信息、屬性的話,那麼子類沒法加強。
二、子類實現了以後需求沒法變動,加強的內容是固定的。post

第一個缺點就拿之前在學JDBC的時候來講:

開始想要本身寫一個簡易的JDBC鏈接池,鏈接池由List 來管理。顯然咱們的對象是Connection,當寫到close()方法的時候卡住了。

由於咱們想要的功能是:調用close()是Connection返回到「鏈接池」(集合)中,而不是關閉掉。

此時咱們不能使用繼承父類的方式來實現加強。由於Connection對象是由數據庫廠商來實現的,在獲得Connection對象的時候綁定了各類信息(數據庫的username、password、具體的數據庫是啥等等)。咱們子類繼承Connection是沒法獲得對應的數據的!就更別說調用close()方法了。

第二點我也舉個例子:

如今我設計一個電話類:

public class Phone {
    // 能夠打電話
    public void call() {
        System.out.println("打電話給周圍的人關注公衆號Java3y");
    }
}

此時,我想打電話以前能聽彩鈴,因而我繼承Phone類,實現我想要的功能。

public class MusicPhone extends Phone {

    // 聽彩鈴
    public void listenMusic() {
        System.out.println("我懷念的是無話不說,我懷念的是一塊兒作夢~~~~~~");
    }

    @Override
    public void call() {

        // 在打電話以前聽彩鈴
        listenMusic();

        super.call();
    }
}

咱們的功能就作好了。此時,我又忽然想實現多一個需求了,我想要聽完電話以後告訴我一下當前的時間是多少。沒事,咱們又繼承來加強一下:

// 這裏繼承的是MusicPhone類
public class GiveCurrentTimePhone extends MusicPhone {

    // 給出當前的時間
    public void currentTime() {
        System.out.println("當前的時間是:" + System.currentTimeMillis());
    }

    @Override
    public void call() {
        super.call();

        // 打完電話提示如今的時間是多少啦
        currentTime();
    }
}

因此咱們仍是能夠完成這種需求的。

但是我需求如今又想變了:

我不想聽彩鈴了,只想聽完電話通知一下時間就行了(但是咱們的通知時間電話類是繼承在聽彩鈴的電話類基礎之上的)。

我又有可能:我想在聽電話以前報告一下時間,聽完電話聽音樂。

若是需求變更很大的狀況下,而咱們又用繼承的方式來實現這樣會致使一種現象:類爆炸(類數量激增)!而且繼承的層次可能會比較多。

因此,咱們能夠看到子類繼承父類這種方式來擴展是十分侷限的,不靈活。

所以就引出了裝飾模式

裝飾模式實例

首先咱們來看看裝飾模式是怎麼用的吧。

示例

一、定義Component接口Phone和具體實現IphoneX

電話接口:

// 一個良好的設計是抽取成接口或者抽象類的
public interface Phone {

    // 能夠打電話
    void call();
}

具體的實現:

public class IphoneX implements Phone {


    @Override
    public void call() {
        System.out.println("打電話給周圍的人關注公衆號Java3y");
    }
}

二、定義裝飾器 PhoneDecrate,它實現了接口Phone,以組合的方式接收具體實現類IphoneX。

// 裝飾器,實現接口
public abstract class PhoneDecorate implements Phone {

    // 以組合的方式來獲取默認實現類
    private Phone phone;
    public PhoneDecorate(Phone phone) {
        this.phone = phone;
    }

    @Override
    public void call() {
        phone.call();
    }
}

三、有了裝飾器之後,就能夠經過繼承裝飾器來進行功能擴展了。

定義須要擴展的裝飾器。

咱們想要在打電話以前聽音樂:

// 繼承着裝飾器PhoneDecorate來擴展
public class MusicPhone extends PhoneDecorate {

    public MusicPhone(Phone phone) {
        super(phone);
    }

    // 定義想要擴展的功能
    public void listenMusic() {

        System.out.println("繼續跑 帶着赤子的驕傲,生命的閃耀不堅持到底怎能看到,與其苟延殘喘不如縱情燃燒");

    }

    // 重寫打電話的方法
    @Override
    public void call() {

        // 在打電話以前聽音樂
        listenMusic();
        super.call();
    }
}

如今我也想在打完電話後通知當前的時間,因而咱們也繼承裝飾類來擴展:

// 這裏繼承的是裝飾器類PhoneDecorate
public class GiveCurrentTimePhone extends PhoneDecorate  {


    public GiveCurrentTimePhone(Phone phone) {
        super(phone);
    }

    // 自定義想要實現的功能:給出當前的時間
    public void currentTime() {
        System.out.println("當前的時間是:" + System.currentTimeMillis());
    }

    // 重寫要加強的方法
    @Override
    public void call() {
        super.call();
        // 打完電話後通知一下當前時間
        currentTime();
    }
}

需求變動

就目前這樣看起來,比我直接繼承父類要麻煩,而功能效果是同樣的。那麼裝飾模式的優點在哪呢?

若是此時,我不想在打電話以前聽到彩鈴了,很簡單:咱們不裝飾它就行了!

此時,我想在打電話前報告一下時間,在打完電話以後聽彩鈴。

注意:
雖說要改動類中的代碼,可是這種改動是合理的。 由於我定義出的GiveCurrentTimePhone類和MusicPhone類自己從語義上就沒有規定擴展功能的執行順序。

而繼承不同:先繼承Phone->實現MusicPhone->再繼承MusicPhone實現GiveCurrentTimePhone。這是固定的,從繼承的邏輯上已經寫死了具體的代碼,是難以改變的。

因此咱們仍是能夠很簡單地完成功能:

示例解說

可能有的同窗在看完上面的代碼以後,仍是迷迷糊糊地不知道裝飾模式是怎麼實現「裝飾」的。下面我就再來解析一下:

第一步:咱們有一個Phone接口,該接口定義了Phone的功能

第二步:咱們有一個最簡單的實現類iPhoneX

第三步:寫一個裝飾器抽象類PhoneDecorate,以組合(構造函數傳遞)的方式接收咱們最簡單的實現類iPhoneX。其實裝飾器抽象類的做用就是代理(核心的功能仍是由最簡單的實現類iPhoneX來作,只不過在擴展的時候能夠添加一些沒有的功能而已)。

第四步:想要擴展什麼功能,就繼承PhoneDecorate裝飾器抽象類,將想要加強的對象(最簡單的實現類iPhoneX或者已經被加強過的對象)傳進去,完成咱們的擴展!
再來看看下面的圖,就懂了!

每每咱們的代碼能夠省略起來,成了這個樣子(是否是和IO的很是像!)

// 先加強聽音樂的功能,再加強通知時間的功能
Phone phone = new GiveCurrentTimePhone(new MusicPhone(new IphoneX()));

結果是同樣的:

應用

Java中的IO

JavaI/O庫的對象結構圖以下:

抽象被裝飾者(Component)角色:由InputStream扮演。這是一個抽象類,爲各類子類型提供統一的接口。

具體被裝飾者(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等類扮演。它們實現了抽象構件角色所規定的接口。

抽象裝飾(Decorator)角色:由FilterInputStream扮演。它實現了InputStream所規定的接口。

具體裝飾(ConcreteDecorator)角色:由幾個類扮演,分別是BufferedInputStream、DataInputStream以及兩個不經常使用到的類LineNumberInputStream、PushbackInputStream。

抽象被裝飾者InputStream

具體被裝飾者FileInputStream

public class FileInputStream extends InputStream
{}

抽象裝飾者類FilterInputStream

具體裝飾角色PushbackInputStream

使用I/O流讀取文件內容的簡單操做示例。

FileInputStream對象至關於原始的被裝飾的對象, 而BufferedInputStream對象和DataInputStream對象則至關於裝飾器。

使用Decorator設計模式加強request對象

有一種狀況下,必須使用Decorator設計模式:即被加強的對象,開發人員只能獲得它的對象,沒法獲得它的class文件。

好比request、response對象,開發人員之因此在servlet中能經過sun公司定義的HttpServletRequest\response接口去操做這些對象,是由於Tomcat服務器廠商編寫了request、response接口的實現類。web服務器在調用servlet時,會用這些接口的實現類建立出對象,而後傳遞給servlet程序。

此種狀況下,因爲開發人員根本不知道服務器廠商編寫的request、response接口的實現類是哪一個?在程序中只能拿到服務器廠商提供的對象,所以就只能採用Decorator設計模式對這些對象進行加強。

Decorator設計模式的實現

一、首先看須要被加強對象繼承了什麼接口或父類,編寫一個類也去繼承這些接口或父類。
二、在類中定義一個變量,變量類型即需加強對象的類型。
三、在類中定義一個構造函數,接收需加強的對象。
四、覆蓋需加強的方法,編寫加強的代碼。

使用Decorator設計模式加強request對象

Servlet API 中提供了一個request對象的Decorator設計模式的默認實現類HttpServletRequestWrapper,HttpServletRequestWrapper 類實現了request 接口中的全部方法,但這些方法的內部實現都是僅僅調用了一下所包裝的的 request 對象的對應方法,以免用戶在對request對象進行加強時須要實現request接口中的全部方法。

編寫一個用於處理中文亂碼的過濾器CharacterEncodingFilter,代碼以下:

package me.gacl.web.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName: CharacterEncodingFilter
* @Description: 此過濾器用來解決解決get、post請求方式下的中文亂碼問題
* @author: 孤傲蒼狼
* @date: 2014-8-31 下午11:09:37
*
*/ 
public class CharacterEncodingFilter implements Filter {

    private FilterConfig filterConfig = null;
    //設置默認的字符編碼
    private String defaultCharset = "UTF-8";

    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //獲得在web.xml中配置的字符編碼
        String charset = filterConfig.getInitParameter("charset");
        if(charset==null){
            charset = defaultCharset;
        }
        request.setCharacterEncoding(charset);
        response.setCharacterEncoding(charset);
        response.setContentType("text/html;charset="+charset);
        
        MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(request);
        chain.doFilter(requestWrapper, response);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        //獲得過濾器的初始化配置信息
        this.filterConfig = filterConfig;
    }
    
    public void destroy() {

    }
}

/**
* @ClassName: MyCharacterEncodingRequest
* @Description: Servlet API中提供了一個request對象的Decorator設計模式的默認實現類HttpServletRequestWrapper,
* (HttpServletRequestWrapper類實現了request接口中的全部方法,但這些方法的內部實現都是僅僅調用了一下所包裝的的 request對象的對應方法)
* 以免用戶在對request對象進行加強時須要實現request接口中的全部方法。
* 因此當須要加強request對象時,只須要寫一個類繼承HttpServletRequestWrapper類,而後在重寫須要加強的方法便可
* @author: 孤傲蒼狼
* @date: 2014-9-2 下午10:42:57
*     1.實現與被加強對象相同的接口 
    二、定義一個變量記住被加強對象
    三、定義一個構造函數,接收被加強對象
    四、覆蓋須要加強的方法
    五、對於不想加強的方法,直接調用被加強對象(目標對象)的方法
*/ 
class MyCharacterEncodingRequest extends HttpServletRequestWrapper{
    //定義一個變量記住被加強對象(request對象是須要被加強的對象)
    private HttpServletRequest request;
    //定義一個構造函數,接收被加強對象
    public MyCharacterEncodingRequest(HttpServletRequest request) {
        super(request);
        this.request = request;
    }
    /* 覆蓋須要加強的getParameter方法
     * @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
     */
    @Override
    public String getParameter(String name) {
        try{
            //獲取參數的值
            String value= this.request.getParameter(name);
            if(value==null){
                return null;
            }
            //若是不是以get方式提交數據的,就直接返回獲取到的值
            if(!this.request.getMethod().equalsIgnoreCase("get")) {
                return value;
            }else{
                //若是是以get方式提交數據的,就對獲取到的值進行轉碼處理
                value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
                return value;
            }
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

優缺點

優勢:
一、裝飾類和被裝飾類能夠獨立發展,不會相互耦合,互相都不用知道對方的存在
二、裝飾模式是繼承的一個替代模式,不管包裝多少層,返回的對象都是is-a的關係(上面的例子:包裝完仍是Phone類型)。對於擴展一個對象的功能,裝飾模式比繼承更加靈活性,不會致使類的個數急劇增長。
三、裝飾模式能夠動態擴展一個實現類的功能,只要繼承了裝飾器就能夠動態擴展想要的功能了。
四、能夠對一個對象進行屢次裝飾,經過使用不一樣的具體裝飾類以及這些裝飾類的排列組合,能夠創造出不少不一樣行爲的組合,獲得功能更爲強大的對象。
五、用戶能夠根據須要增長新的具體構件類和具體裝飾類,原有類庫代碼無須改變,符合「開閉原則」。

缺點:
一、多層裝飾比較複雜,提升了系統的複雜度,不利於調試。
二、使用裝飾模式會產生比使用繼承關係更多的對象。更多的對象會使得查錯變得困難,特別是這些對象看上去都很相像。
三、使用裝飾模式進行系統設計時將產生不少小對象,這些對象的區別在於它們之間相互鏈接的方式有所不一樣,而不是它們的類或者屬性值有所不一樣,大量小對象的產生勢必會佔用更多的系統資源,在必定程序上影響程序的性能。

總結

對象加強的三種方式:

  • 繼承
  • 裝飾器模式
  • 代理模式

那麼只要遇到Java提供給咱們的API不夠用,加強一下就好了。在寫代碼時,某個類被寫死了,功能不夠用,加強一下就能夠了!本文介紹了繼承和裝飾模式進行加強的方式,代理模式後續專門進行講解。

裝飾器模式適用於在不影響其餘對象的狀況下,以動態、透明的方式給單個對象添加職責。

特色
一、裝飾者和被裝飾者有相同的接口(或有相同的父類)。
二、裝飾者保存了一個被裝飾者的引用。
三、在運行時動態地爲對象添加屬性,沒必要改變對象的結構。

參考資料:
包裝模式就是這麼簡單啦
裝飾器模式
JAVA設計模式初探之裝飾者模式
javaweb學習總結(四十三)——Filter高級開發

相關文章
相關標籤/搜索