使用組合的設計模式——美顏相機中的裝飾者模式

這是設計模式系列的第二篇,系列文章目錄以下:設計模式

  1. 一句話總結異曲同工的設計模式:工廠模式=?策略模式=?模版方法模式bash

  2. 使用組合的設計模式 —— 美顏相機中的裝飾者模式ide

  3. 使用組合的設計模式 —— 找對象要用的遠程代理模式post

  4. 用設計模式去掉不必的狀態變量 —— 狀態模式ui

幾乎全部的設計模式都是經過增長一層抽象來解決問題。this

上一篇中提到的三個設計模式經過相同的手段來達到相同的目的:它們經過接口和抽象方法來新增抽象層以應對變化。spa

這一系列的後續幾篇中會提到的四個設計模式經過相同的手段來達到不一樣的目的:它們經過新增一個類並持有原有類的方式實現對其擴展或限制。設計

這一篇先來看看裝飾者模式。代理

裝飾者模式就好像美顏相機,經過添加不一樣的裝飾品,它可讓你變成另外一個你。(雖然可能面目全非,但本質上仍是你)code

只複用類型

假設有四種飾品:耳環、鑽石、黃金、羽毛。不一樣裝飾品有不一樣價格,一般咱們會這樣作抽象:

//抽象飾品
public abstract class Accessory {
    public abstract String name();//飾品名稱
    public abstract int cost();//飾品價格
}

//耳環
public class Ring extends Accessory {
    @Override
    public String name() { return "Ring"; }
    @Override
    public int cost() { return 20; }
}

//鑽石
public class Diamond extends Accessory {
    @Override
    public String name() { return "Diamond"; }
    @Override
    public int cost() { return 1000; }
}

//黃金
public class Gold extends Accessory {
    @Override
    public String name() { return "Gold"; }
    @Override
    public int cost() { return 300; }
}

//羽毛
public class Feather extends Accessory {
    @Override
    public String name() { return "Feather"; }
    @Override
    public int cost() { return 90; }
}
複製代碼

現推出兩款新飾品:黃金耳環,羽毛黃金耳環。一樣的思路,使用繼承能夠解決問題:

public class GoldRing extends Accessory {
    @Override
    public String name() { return "GoldRing"; }
    @Override
    public int cost() { return 320; }
}

public class FeatherGoldRing extends Accessory {
    @Override
    public String name() {  "FeatherGoldRing"; }
    @Override
    public int cost() { return 1110; }
}
複製代碼
  • 若是繼續推出更多的新品,好比羽毛耳環,鑽石耳環,羽毛鑽石耳環。。。每一個新產品都用一個新的類表示,這樣就會遇到子類膨脹的問題。
  • 除此以外,繼承還有一個更致命的缺點:對單個類型的飾品沒有統一的控制力。若是黃金漲價了,咱們須要分別修改GoldRingFeatherGoldRing的價格,若是和黃金相關的飾品有好幾十個,那簡直是一場噩夢。
  • 在計算GoldRing價格的時候,咱們並無複用現有代碼,即沒有複用GoldRing已經定義的cost()行爲,而只是經過繼承複用了類型(GoldRing是一個Accessory)。只複用類型而沒有複用行爲的後果是:當Gold漲價時,GoldRing無感知。

有沒有一種比繼承更好的方案在現有飾品基礎上擴展新的飾品?

既複用類型又複用行爲

採用組合的方式就能夠實現既複用類型又複用行爲:

public class Gold extends Accessory {
    private Accessory accessory;
    public Gold(Accessory accessory) { this.accessory = accessory; }
    
    @Override
    public String name() {
        return "Gold " + accessory.name();
    }
    @Override
    public int cost() {
        return 300 + accessory.cost();
    }
}

public class Feather extends Accessory {
    private Accessory accessory;
    public Feather(Accessory accessory) { this.accessory = accessory; }

    @Override
    public String name() {
        return "Feather " + accessory.name();
    }
    @Override
    public int cost() {
        return 90 + accessory.cost();
    }
}
複製代碼
  • 上述四種飾品其實分爲兩類,耳環屬於基本飾品,而羽毛、黃金、鑽石屬於附加飾品,附加飾品能夠裝飾基本飾品。
  • 附加飾品和基礎飾品擁有相同的超類型Accessory,但附加飾品還經過組合的方式持有一個超類型實例,這樣就能夠經過注入超類型的方式將其和任意基礎飾品組合到一塊兒造成新的飾品。

用組合的方式實現羽毛黃金耳環:

Accessory ring = new Gold(new Feather(new Ring()));
複製代碼
  • 爲了說明裝飾與被裝飾的關係,使用了帶有俄羅斯套娃既視感的代碼(雖然這樣的代碼可讀性較差)。
  • Ring做爲基礎飾品被Feather裝飾成羽毛耳環,羽毛耳環接着被Gold裝飾成換羽毛黃金耳環。
  • 過程當中並無爲羽毛黃金耳環新增一個叫FeatherGoldRing的子類,而是複用了現有的FeatherGold的行爲。這樣就解決了子類氾濫和控制力的問題。若是黃金漲價,只須要修改Gold.cost(),全部被Gold裝飾的飾品價格都會隨之而漲。
  • 這個方案還有一個更有用的好處:在運行時動態新增類型。經過繼承新增的類型都是在編譯時定死的,而經過組合的方式只要新增一行俄羅斯套娃式的代碼,程序運行起來後就新增了一個類型,好比要新增「雙倍黃金羽毛耳環」這個類型,只須要以下的代碼:
Accessory ring = new Gold(new Gold(new Feather(new Ring())));
複製代碼

抽象的裝飾者?

新的需求來了:基礎飾品鑲嵌附加飾品收取 10% 的一次性加工費。咱們能夠爲全部附加飾品增長一層抽象:

public abstract class Decorator extends Accessory{
    private Accessory accessory;
    public Decorator(Accessory accessory) { this.accessory = accessory; }

    @Override
    public int cost() {
        return  1.1 * accessory.cost();
    }
}
複製代碼
  • Decorator經過組合持有超類型Accessory且規定了在構造時必須注入超類型,它還定義了鑲嵌加工費的收費標準。
  • 如今就能夠像這樣從新定義附加飾品:
public class Gold extends Decorator {
    public Gold(Accessory accessory){ super(accessory); }

    @Override
    public String name() {
        return "Gold " + accessory.name();
    }
    @Override
    public int cost() {
        return 300 + super.cost();
    }
}
複製代碼
  • 其實對於裝飾者模式來講,爲裝飾者定義一個抽象的父類不是必須的,只要知足繼承超類型,以及持有超類型引用這兩點就是裝飾者模式。除非須要統一操做全部裝飾者,好比在美顏相機這個場景中,須要經過遍歷找出全部附加飾品。

題外話

使用裝飾者模式後,GoldFeather中有一些樣板代碼,若是使用Kotlin能夠將代碼簡化以下:

class Feather(val accessory: Accessory) : Accessory by accessory {
    override fun name(): String = "Feather" + accessory.name()
    override fun cost(): Int = 90 + accessory.cost()
}

class Gold(val accessory: Accessory) : Accessory by accessory {
    override fun name(): String = "Gold" + accessory.name()
    override fun cost(): Int = 300 + accessory.cost()
}
複製代碼

經過by關鍵詞把類委託給一個具備超類型的成員變量accessory,若是不重寫,類中的name()cost()的默認實現都將轉發給accessory

總結

  • 裝飾者模式是一種複用原有類並對其進行擴展的方式,它是繼承的替代方法。
  • 裝飾者模式經過繼承原有類型實現複用類型。這一點很重要,由於全部使用原有類型的地方不須要修改代碼就能夠替換成裝飾者。
  • 裝飾者模式經過組合持有原有類實例實現複用行爲。
  • 裝飾者模式經過在調用原有類方法的先後插入新的邏輯實現功能擴展。
  • 裝飾者模式符合開閉原則,即在新增功能的時候沒有修改原有代碼。
  • 裝飾者模式特別適用於子類型之間能夠有隨機組合的場景,好比美顏相機的各類道具組合以後造成新的道具。

運用組合的設計模式不止裝飾者一個,該系列的後續文章會繼續分析「組合」在設計模式中的運用。

推薦閱讀

相關文章
相關標籤/搜索