裝飾模式指動態地給一個對象添加一些額外的職責,就增長功能來講,裝飾模式比生成子類更爲靈活。html
這種類型的設計模式屬於結構型模式,它是做爲現有的類的一個包裝。java
這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。web
通常的,咱們爲了擴展一個類常用繼承方式實現,因爲繼承爲類引入靜態特徵,而且隨着擴展功能的增多,子類會很膨脹。在不想增長不少子類的狀況下擴展類時可使用裝飾器模式。數據庫
不少時候咱們可能對Java提供給咱們的對象不滿意,不能知足咱們的功能。此時咱們就想對Java原對象進行加強,可以實現咱們想要的功能就好~設計模式
通常來講,實現對象加強有三種方式:服務器
一、繼 承:繼承父類,子類擴展
二、裝飾器模式:使用「包裝」的方式來加強對象
三、代理模式:靜態代理和動態代理app
繼承ide
最簡單的方式就是繼承父類,子類擴展來達到目的。雖然簡單,可是這種方式的缺陷很是大:函數
一、若是父類是帶有數據、信息、屬性的話,那麼子類沒法加強。
二、子類實現了以後需求沒法變動,加強的內容是固定的。post
第一個缺點就拿之前在學JDBC的時候來講:
開始想要本身寫一個簡易的JDBC鏈接池,鏈接池由List
由於咱們想要的功能是:調用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()));
結果是同樣的:
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設計模式:即被加強的對象,開發人員只能獲得它的對象,沒法獲得它的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高級開發