對複合(協做)算法/策略的封裝方法——裝飾模式總結

前言

裝飾模式顧名思義就是在不改變原對象的前提下,將新功能優雅的附加(裝飾)到該對象上,能夠實現對複合算法(策略)的優雅封裝、對須要協做的算法(策略)進行有機組合。html

裝飾模式和策略模式用法相似,可是也有明顯區別——策略模式運行時只能運行一個算法,且其各個算法(算法族)之間必須相互獨立,不能有聯繫,裝飾模式沒有這些約束。java

裝飾模式和策略模式同樣,也是對繼承的一種替代方案——使用對象組合的方式,作到運行時裝飾對象,從而優雅的替代死板的繼承。算法

另外,裝飾模式是對象的結構型模式。編程

再議繼承的缺陷

在策略模式:繼承、組合和接口用法——策略模式複習總結,裏有一個鴨子的案例,說到了繼承的種種侷限性,這能夠用策略模式(組合+接口)改進。一樣,咱們也能夠用裝飾模式。設計模式

看一個新的例子——作手抓餅的例子,如今要實現一個作手抓餅的點餐程序,通常人都會加一個雞蛋,或者烤腸,辣條等等,加不一樣的料,價格確定也不同,下面看代碼:安全

public class Cake { // 表明餅
    protected String getInfo() {
        return "這是一個白餅";
    }

    protected double getCost() {
        return 2.0D; // 賣兩元
    }
}

下面是加了雞蛋的餅,和加雞蛋和烤腸的餅架構

public class CakeWithEgg extends Cake {
    @Override
    public String getInfo() {
        return super.getInfo() + "加1雞蛋";
    }

    @Override
    public double getCost() {
        return super.getCost() + 2;
    }
}
//////////////////////////////
public class CakeWithEggSausage extends CakeWithEgg {
    @Override
    public String getInfo() {
        return super.getInfo() + "加1個烤腸";
    }

    @Override
    public double getCost() {
        return super.getCost() + 2;
    }
}

客戶端調用以下:框架

public class Main {
    public static void main(String[] args) {
        Cake cake = new Cake();
        System.out.println(cake.getInfo() + ",價格:" + cake.getCost());

        Cake cake1 = new CakeWithEgg();
        System.out.println(cake1.getInfo() + ",價格:" + cake1.getCost());

        Cake cake2 = new CakeWithEggSausage();
        System.out.println(cake2.getInfo() + ",價格:" + cake2.getCost());
    }
}

很簡單,很快就實現好了,UML 類圖以下:ide

看似很正常,很完美,可是此時,有新的需求了——加 N 個雞蛋或者烤腸等。。。此時會發現,該程序已經沒法正常的擴展了,若是仍是依樣畫葫蘆的添加新類,經過繼承。。。那麼必將致使系統的類「爆炸」——成爲垃圾代碼的典範案例。。。函數

小結

利用繼承擴展類的行爲,在代碼編譯期間就決定了行爲是什麼了,且全部它的子類都要無腦的接收父類的內容。。。這在某些場景下是不太合理的(聯繫策略模式裏鴨子的例子),而使用組合就能夠規避這樣的問題。

組合能夠動態的組合對象,在不改變現有類的基礎上,給這個類添加新的行爲,既不會對舊代碼引入 bug,也能增長新功能,這就是 OCP(open close principle)——開閉原則的體現。

設計原則:開閉原則(OCP)

額外提一句:軟件設計在考慮基本原則以前,也要綜合考慮 deadline,項目規模,等因素,一句話——屈於現實,有時候容許代碼寫的不符合設計原則,可是心中要有一條'準線',而不是一直聽之任之,甚至隨波逐流,無所謂的態度。。。

OCP定義:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。

核心思想:面向抽象編程——用抽象構建框架,用實現擴展細節,其實我以爲核心就是一句話,全部的開源框架和設計模式的精髓——加間接層,只要有變化,或者感受彆扭,就能夠抽象不變的代碼,而後加間接層,擴展功能。

OCP 的目的在於面對需求的改變而保持系統的相對穩定,從而使得系統能夠很容易的從一個版本升級到另外一個版本。而實際生產環境裏,絕對封閉的系統是不存在的。不管模塊怎麼封閉,到最後總仍是有一些沒法封閉的變化。

設計的基本思路

既然不能作到徹底封閉,那就應該只封閉變化的部分:

一、作粗粒度的隔離:把變化的模塊和穩定的模塊先區分開

二、作細粒度的隔離:在變化的模塊裏,嘗試對邏輯段落進行封裝,把那些實在沒法封閉的部分抽象出來,即進行細粒度的隔離——好比使用抽象類,接口等

三、要容許擴展,當系統的變化來臨時,要及時的作出反應。而不是一味的就知道修改已有的業務代碼

矛盾

實際上,變化來的越早,抽象就越容易,代碼的維護也就越容易

可是,當項目接近於完成時,纔來的一些需求,則會使抽象變得很困難。這個困難並非抽象自己的困難,抽象自己不難,難在系統的架構已經完成,修改牽扯的方面太多而使得抽象工做變得很困難。

這是一個矛盾點

拆分原則

總的來講:

一、不能懼怕改變——當新需求到來時,首先要作的不是上來就修改已有的業務代碼,而是儘量的將變化抽象出來進行隔離,而後再進行擴展。

二、面對需求的變化,對程序的修改應該是儘量經過添加代碼來實現,而不是經過修改已有代碼來實現。固然,現實中,各類狀況一綜合,你們也都知道,呵呵了。。。

組合和委託的運用——裝飾模式的實現

下面採起一些新的設計思路;以餅Cake爲主體,咱們認爲它是被裝飾的實體,如今要實現,在運行時動態的用雞蛋,烤腸,辣條等裝飾者,去裝飾餅 cake。全程只 new 一個餅便可,若是客戶想要一個加雞蛋的餅,就用雞蛋去裝飾這個餅,若是想要 N 個雞蛋,就用 N 個雞蛋去裝飾這個餅,烤腸等同理。最後再調用 cost,info 等方法,將餅的信息和價錢算出來。

其實,裝飾理解爲包裝更合適。聯繫 Java 的 I/O API,其實就是這麼用的,各類 buffer 的流去包裝字節,字符流。

如此一來,咱們能夠把看似不一樣的東西,餅和雞蛋不一樣,看作同一種東西來使用,如何實現呢?天然就用到了組合+委託的設計思想。

既然餅和雞蛋兩個不相干的東西能被當作一個東西來用,它們確定都要繼承同一個類型:手抓餅是具體的被裝飾的類——Cake,咱們能夠給它設計一個抽象的父類,只表明餅——DefaultCake,而雞蛋是一個具體的裝飾類——EggDecorator,它要有一個抽象的(或者非抽象)父類——裝飾者類 Decorator。同時咱們讓裝飾者類 Decorator 也繼承 DefaultCake 便可。

下面看代碼:

public abstract class DefaultCake { // 表明餅——抽象的餅類,也就是抽象的被裝飾類
    // 注意,該類也能夠用接口實現,且這個角色不是必須的,也能夠直接就是具體的被裝飾類
    protected abstract String getInfo();
    protected abstract double getCost();
}
//////////////////////////////////////////
public class Cake2 extends DefaultCake { // 具體的被裝飾類
    @Override
    protected String getInfo() {
        return "這是一個白餅";
    }

    @Override
    protected double getCost() {
        return 2.0D;
    }
}

如上能解決兩個類的類型一致問題,下面讓兩個類關聯起來——使用組合+委託,咱們目的是讓裝飾者——EggDecorator去裝飾被裝飾的類——Cake,能夠把抽象的被裝飾類DefaultCake組合到裝飾者Decorator類裏,來關聯裝飾類和被裝飾類,而後聯繫策略模式,經過構造器或者 setter 方法,把抽象的被裝飾類DefaultCake注入進裝飾類Decorator——這就叫委託,即裝飾類Decorator將新的功能,委託給被裝飾類DefaultCake去實現。

public class Decorator extends DefaultCake { // 裝飾類的父類。這個類也能夠設計爲抽象的,前提裝飾類要額外作一些事情的時候
    private DefaultCake defaultCake; // 經過組合抽象的被裝飾父類,來關聯裝飾類和具體的被裝飾類

    public Decorator(DefaultCake defaultCake) { // 經過構造器注入
        // 這樣能夠把被裝飾類DefaultCake傳入裝飾類Decorator——這也叫委託,即裝飾類Decorator將裝飾的功能,委託給被裝飾類DefaultCake去實現
        this.defaultCake = defaultCake;
    }

    @Override
    protected String getInfo() {
        return this.defaultCake.getInfo();
    }

    @Override
    protected double getCost() {
        return this.defaultCake.getCost();
    }
}
//////////////////////////////
public class EggDecorator extends Decorator { // 具體的裝飾類——雞蛋
    public EggDecorator(DefaultCake defaultCake) { // 這裏必需要實現有參構造器,由於父類寫了有參構造器
        super(defaultCake);
    }

    @Override
    protected String getInfo() {
        return super.getInfo() + " 加1個雞蛋";
    }

    @Override
    protected double getCost() {
        return super.getCost() + 2;
    }
}
/////////////////////////////
public class SausageDcorator extends Decorator { // 具體的裝飾類——烤腸
    public SausageDcorator(DefaultCake defaultCake) {
        super(defaultCake);
    }

    @Override
    protected String getInfo() {
        return super.getInfo() + " 加1個烤腸";
    }

    @Override
    protected double getCost() {
        return super.getCost() + 2;
    }
}

客戶端調用:

public class Main2 {
    public static void main(String[] args) {
        DefaultCake cake = new Cake2();
        cake = new EggDecorator(cake); // 用雞蛋這個具體的裝飾者去包裝被裝飾者——餅cake,看起來很像 I/O API
        cake = new EggDecorator(cake); // 再加一個雞蛋
        cake = new EggDecorator(cake); // 理論上能夠加 N 個,可是咱們只用一個類就能解決這個需求
        cake = new SausageDcorator(cake); // 再加一個烤腸
        System.out.println(cake.getInfo() + ",價格:" + cake.getCost()); // 這是一個白餅 加1個雞蛋 加1個雞蛋 加1個雞蛋 加1個烤腸,價格:10.0
    }
}

其實也不難,下面是類圖:

裝飾模式的特色

一、裝飾者和被裝飾者要有相同的超類型

類型的一致性,經過繼承共同父類解決

二、裝飾者要把裝飾的責任委託給被裝飾者,這樣處理,裝飾者能在委託以前或者以後,加上本身的專屬行爲,以達到特殊目的

三、經過組合+委託,實現動態的,運行時,不限量的爲對象增長新功能

四、保持了接口的透明性——OCP 原則

由於裝飾者和被裝飾者類型具備一致性,因此,它們的 API 也具備一致性,也就實現了接口的透明性,即不會有繼承的弊端——覆蓋掉父類方法,具體的被裝飾類不論被裝飾多少次,其父類(或者接口)的 API 都不會被修改,同時這也體現了遞歸的思想。

再次強調:裝飾模式使用繼承,是爲了實現類型的一致性,而不是爲了擴展類的功能。

裝飾模式的適用場景

一、適合替代繼承,給類添加新的職責

二、比策略模式更靈活——能夠在運行時動態的給一個對象添加新功能,也能夠撤銷已經添加的新功能,能夠同時封裝多個算法(策略)去完成一件事。

裝飾模式的優勢

其實不必說太多了,前面包括策略模式已經說到了不少繼承的缺陷,也說到了策略模式的一個缺點——算法族的算法必須獨立和平等,裝飾模式能夠彌補這些缺陷

一、裝飾模式比繼承靈活,能夠在不改變對象的前提下,擴展對象。若是隻使用繼承擴展類,那麼當新功能較多時,會致使類的膨脹和複雜,且子類不必定都要具有父類的這些特性,聯繫策略模式裏鴨子的例子。並且,繼承對類的擴展是靜態的,在編譯時就要肯定,而裝飾模式是動態的擴展

二、說到算法族的平等和獨立,這是策略模式的侷限性,可是裝飾模式就能夠經過排列組合,實現算法之間的協做和組合,也是題目的意思

不叫缺點的「缺點」

增長了系統的類數量,增長了代碼量,代碼量多了,天然程序就顯得複雜一些,可是我認爲,瑕不掩瑜,無所謂,能夠說是硬給扣的一些帽子,大膽的用吧,若是有人看不懂,只能說對方編程能力比較水,OO 能力比較差。

JDK 使用裝飾模式的例子

I/O 包:源碼沒什麼分析的必要了,很直觀

還有對集合的線程安全的包裝API,好比java.util 包裏的 Collections.synchronizedMap()等;

相關文章
相關標籤/搜索