只一篇就夠了·設(shè)計模式(2) - 裝飾者模式

裝飾者模式(Decorator Pattern)是在不必改變原類文件和使用繼承的情況下堕伪,動態(tài)地擴展一個對象的功能,它是通過創(chuàng)建一個包裝對象,也就是裝飾來包裹真實的對象。
裝飾者是代替繼承擴展功能另外一種方式,裝飾者模式遵循了多用組合少用繼承和對擴展開放對修改關(guān)閉的設(shè)計原則蚌成。

這一次用兩個例子來說明,一個關(guān)于車的凛捏,一個是項目實際中經(jīng)常用到的驗證問題。
假如你有一天醒了芹缔,起來發(fā)現(xiàn)有人送了一輛法拉利488坯癣,那么有個性的你怎么會只滿足車輛本身的性能和外觀,這時候你會想著按照自己的意愿修改也就是改造最欠。改了顏色示罗、花紋、排氣管等等要再改就比較麻煩了芝硬,既然是做夢蚜点,索性就做一點智能的東西,隨意修改改裝配件拌阴,還不用破壞車子本來的樣子绍绘,接下來就用裝飾者模式來做做夢~

類圖

類圖不是目的,只是方便理解

[圖片上傳失敗...(image-bfea17-1527174192660)]

前面提到了裝飾者模式是創(chuàng)建包裝對象,所以裝飾者模式里面的類不管是具體的組件還是裝飾者組件都是實現(xiàn)同樣的接口或者類陪拘。
Component是裝飾的裝飾接口厂镇,ConcreteComponent是具體的裝飾組件,DecoratorComponent是裝飾者組件左刽,裝飾者模式的核心就是用裝飾者去裝飾具體的組件捺信,同樣的一個裝飾者組件也可以當(dāng)成被裝飾的具體組件。
在這個類圖中欠痴,可以用DecorateComponentA或者DecorateComponentB去裝飾ConcreteComponent迄靠、DecorateComponentADecorateComponentB喇辽,你要是非常喪心病狂也可以用DecorateComponentA裝飾自己掌挚。

接下來實現(xiàn)自己的汽車智能改裝

實例

現(xiàn)在我們用之前的知識梳理一下如何實現(xiàn)一個汽車的智能改裝。
首先茵臭,需要一個Component也就是組件接口疫诽,因為我們是定義的改裝所以定義成IUpdate,里面有一個叫refit()改裝的行為:

/**
 * 改裝,裝飾者的裝飾接口
 * Created by Carlton on 2016/11/2.
 */
interface IUpdate
{
    /**
     * 改裝
     */
    fun refit() : String
}

有了裝飾者接口旦委,現(xiàn)在還需要一個被裝飾的具體裝飾組件奇徒,要不然妝化那么好,穿那么漂亮給誰看呢缨硝?女為悅己者容摩钙,所以得先要有一個服務(wù)的對象ConcreteComponent,這里定義成Car查辩,這是別人送給你的4s店原裝無改動的法拉利 488:

/**
 * 汽車, 被裝飾的組件
 * Created by Carlton on 2016/11/2.
 */
class Car(var name: String) : IUpdate
{
    override fun refit(): String
    {
        return ""
    }

    override fun toString(): String
    {
        return "Car(name='$name')"
    }
}

現(xiàn)在有了具體的目標胖笛,接下來就需要很多額外的配件來改裝,我們想改變汽車的顏色宜岛、速度长踊、是否需要噴火,所以先定義4個類:RedColor萍倡、BlueColor身弊、Speed、Fire:

/**
 * 紅色改裝
 * Created by Carlton on 2016/11/2.
 */
class RedColor(update: IUpdate) : DecoratorUpdate(update)
{
    override fun refit(): String
    {
        return "${update.refit()} - 紅色的"
    }
}

/**
 * 藍色改裝
 * Created by Carlton on 2016/11/2.
 */
class BlueColor(update: IUpdate) : DecoratorUpdate(update)
{
    override fun refit(): String
    {
        return "${update.refit()} - 藍色的"
    }
}

/**
 * 速度裝飾者
 * Created by Carlton on 2016/11/2.
 */
class Speed(update: IUpdate) : DecoratorUpdate(update)
{
    override fun refit(): String
    {
        return "${update.refit()} - 速度提升一倍"
    }
}

/**
 * 能噴火的裝飾者
 * Created by Carlton on 2016/11/2.
 */
class Fire(update: IUpdate) : DecoratorUpdate(update)
{
    override fun refit(): String
    {
        return "${update.refit()} - 能噴火"
    }

    fun fire(): String
    {
        return "${refit()}:氮氣"
    }
}

注意這里的Fire類列敲,里面多了一個方法叫fire()阱佛,功能是噴氮氣。這是裝飾者模式擴展的汽車原來的行為戴而,可以給每一個裝飾者組件添加多余的功能凑术。

現(xiàn)在,汽車和原材料都準備好了所意,那么:

  • 我想要一輛紅色淮逊、能噴氮氣催首、速度極快的車車
  • 我想要一輛藍色、速度極快的車車
  • 我想要一輛藍色壮莹、速度翅帜、速度的車車
//紅色、氮氣命满、速度提升一倍的Ferrari
val car:Car = Car("Ferrari 488")
val refit = Fire(Speed(RedColor(car))).fire()
println("$car $refit")
//  Car(name='Ferrari 488')  - 紅色的 - 速度提升一倍 - 能噴火:氮氣


// 藍色涝滴、速度極快的
val car:Car = Car("Ferrari 488")
val refit = Speed(BlueColor(car)).refit()
println("$car $refit")
//  Car(name='Ferrari 488')  - 藍色的 - 速度提升一倍

// 藍色、速度胶台、速度
val car:Car = Car("Ferrari 488")
val refit = Speed(Speed(BlueColor(car))).refit()
println("$car $refit")
//  Car(name='Ferrari 488')  - 藍色的 - 速度提升一倍 - 速度提升一倍

既然如此歼疮,那我買一輛自行車也能改裝嘛,所以我們現(xiàn)在新增一個具體的裝飾者組件Bike自行車:

/**
 * 自行車, 具體的裝飾者組件
 * Created by Carlton on 2016/11/2.
 */
class Bike(var name: String) : IUpdate
{
    override fun refit(): String
    {
        return ""
    }

    override fun toString(): String
    {
        return "Bike(name='$name')"
    }
}

一輛藍色诈唬、能噴火韩脏、速度提升的自行車由此誕生:

val bike:Bike = Bike("鳳凰牌自行車")
val refitBike = Speed(Fire(BlueColor(bike))).refit()
println("$bike $refitBike")
//  Bike(name='鳳凰牌自行車')  - 藍色的 - 能噴火 - 速度提升一倍

例子的類圖:

類圖不是目的,只是方便理解

[圖片上傳失敗...(image-6c50e1-1527174192660)]

在這個例子中CarBike都是具體的裝飾者組件铸磅,而繼承自DecoratorUpdate的都是裝飾者組件赡矢,可以自由擴展其他功能,比如Fire中的fire()阅仔,裝飾者組件本身也可以被裝飾吹散。

項目應(yīng)用

以下代碼為Java實現(xiàn)

我們經(jīng)常會遇到一個需求就是對前端的字段驗證,比如字段是不是為空八酒,手機號格式是否正確等等空民,如果對字段的判斷需要新增規(guī)則用裝飾者模式就能愉快的解決這個問題,接下來實現(xiàn)一個驗證的裝飾者羞迷,對字段的驗證通過裝飾者來實現(xiàn)界轩。我們實現(xiàn)一個正則表達式的裝飾者和一個函數(shù)方法的裝飾者(驗證字段可以自定義規(guī)則)。
首先衔瓮,我們需要一個裝飾者的接口:

/**
 * 參數(shù)驗證接口
 * Created by Carlton on 2016/10/31.
 */
public interface IParamValidate<T>
{
    /**
     * 驗證是否通過浊猾,先驗證自身的條件再驗證裝飾者。先驗證被裝飾者热鞍,在驗證自身的條件与殃。
     *
     * @param value 被驗證的字段
     *
     * @return 如果是true驗證通過,如果是false驗證失敗碍现。如果value==null,返回false
     */
    public boolean validate(T value);
}

然后米奸,實現(xiàn)一個具體的裝飾對象EmptyValidate

/**
 * 字段為空的驗證昼接,也是裝飾者中的被裝飾的對象.
 * 如果數(shù)據(jù)類型是null 返回false,如果數(shù)據(jù)類型是CharSequence則使用{@link TextUtils#isEmpty(CharSequence)}判斷字符是否為空悴晰。
 * 所有的true代表驗證通過慢睡,false代表驗證不通過逐工。
 * Created by Carlton on 2016/11/1.
 */
public class EmptyValidate<T> implements IParamValidate<T>
{
    @Override
    public boolean validate(T value)
    {
        return value != null && (!(value instanceof CharSequence) || !TextUtils.isEmpty((CharSequence) value));
    }
}

有了具體的裝飾對象,現(xiàn)在我們實現(xiàn)一個正則表達式的驗證和一個函數(shù)驗證的裝飾者:

/**
 * 驗證的接口漂辐,用于擴展自定義的驗證方式泪喊。
 * 實現(xiàn)的返回必須遵循下面的規(guī)則:
 * <ul>
 * <li>
 * 1、如果value==null則返回false髓涯。
 * </li>
 * <li>
 * 2袒啼、如果返回true則代表驗證通過,如果返回false則代表驗證失敗纬纪。
 * </li>
 * </ul>
 * Created by Carlton on 2016/11/1.
 */
public interface IValidator<T>
{
    /**
     * 自定義的驗證接口方法
     *
     * @param value 被驗證的值
     *
     * @return true是驗證通過蚓再,false驗證失敗
     */
    public boolean validate(T value);
}

/**
 * 函數(shù)驗證,通過一個自定義的函數(shù)來實現(xiàn)驗證包各。如果Validator==null 返回自身驗證返回true摘仅。
 * Created by Carlton on 2016/11/1.
 */
public class FunctionValidate<T> extends ValidateDecorator<T>
{
    private IValidator<T> mValidator;

    public FunctionValidate(IParamValidate<T> validate, IValidator<T> validator)
    {
        super(validate);
        mValidator = validator;
    }

    private boolean selfValidate(T value)
    {
        return mValidator == null || mValidator.validate(value);
    }

    @Override
    public boolean validate(T value)
    {
        return getValidate().validate(value) && selfValidate(value);
    }
}

/**
 * 正則表達式驗證。使用一個正則表達是來匹配驗證问畅。
 * 如果value == null返回false娃属。
 * 如果是true驗證通過,如果是false驗證不通過护姆。
 * Created by Carlton on 2016/11/1.
 */
public class RexValidate extends ValidateDecorator<CharSequence>
{
    private String mPatternString = "\\.";
    public RexValidate(String patternString, IParamValidate<CharSequence> validate)
    {
        super(validate);
        mPatternString = patternString;
    }
    private boolean selfValidate(CharSequence value)
    {
        if(value == null)
        {
            return false;
        }
        Pattern pattern = Pattern.compile(mPatternString);
        Matcher matcher = pattern.matcher(value);
        return matcher.find();
    }

    @Override
    public boolean validate(CharSequence value)
    {
        return getValidate().validate(value) && selfValidate(value);
    }
}

IValidator是函數(shù)驗證的接口矾端,然后就是怎么使用。

  • 驗證一個字段是否是空签则、是否匹配正則“abc”须床、首字母是否是C

實現(xiàn)代碼:

public static void main(String[] args)
{
    String validateValue = "這是被驗證的字段";
    // 驗證是否是空
    EmptyValidate<CharSequence> emptyValidate = new EmptyValidate<>();
    // 驗證是否通過正則表達式'abc'
    RexValidate rxValidate = new RexValidate("abc", emptyValidate);
    // 驗證自定義函數(shù),首字母是否是C
    boolean result = new FunctionValidate<>(rxValidate, new IValidator<CharSequence>() {
        @Override
        public boolean validate(CharSequence value)
        {
            return !(value == null || value.length() > 0) && value.charAt(0) == 'C';
        }
    }).validate(validateValue);
    System.out.println(result);
}

同樣的這些規(guī)則可以任意組合渐裂,自己也能實現(xiàn)其他的驗證規(guī)則豺旬,比如可以把手機號驗證直接實現(xiàn)成一個裝飾者。

總結(jié)

裝飾者模式解決的問題是動態(tài)的將責(zé)任附加到對象上柒凉,若要擴展功能族阅,裝飾者提供了比繼承更有彈性的替代方案。萬物都是平衡的膝捞,裝飾者也有它自己的缺點坦刀,主要就是會產(chǎn)生很多類,在使用的時候不要無腦套用蔬咬,需要自己權(quán)衡是否合適鲤遥。另外java中的I/O Stream是用裝飾者實現(xiàn)的,本來想擴展一個IO Stream對象林艘,想來比較畫蛇添足盖奈,有興趣的可以去了解一下。

??查看更多??

不登高山狐援,不知天之高也钢坦;不臨深溪究孕,不知地之厚也
感謝指點、交流爹凹、喜歡

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末厨诸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子禾酱,更是在濱河造成了極大的恐慌微酬,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宇植,死亡現(xiàn)場離奇詭異得封,居然都是意外死亡,警方通過查閱死者的電腦和手機指郁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門忙上,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人闲坎,你說我怎么就攤上這事疫粥。” “怎么了腰懂?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵梗逮,是天一觀的道長。 經(jīng)常有香客問我绣溜,道長慷彤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任怖喻,我火速辦了婚禮底哗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锚沸。我一直安慰自己跋选,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布哗蜈。 她就那樣靜靜地躺著前标,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天聋袋,我揣著相機與錄音,去河邊找鬼唯鸭。 笑死,一個胖子當(dāng)著我的面吹牛硅确,可吹牛的內(nèi)容都是我干的目溉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼菱农,長吁一口氣:“原來是場噩夢啊……” “哼缭付!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起循未,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤陷猫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后的妖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绣檬,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年嫂粟,在試婚紗的時候發(fā)現(xiàn)自己被綠了娇未。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡星虹,死狀恐怖零抬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宽涌,我是刑警寧澤平夜,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站卸亮,受9級特大地震影響忽妒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兼贸,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一段直、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寝受,春花似錦坷牛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至甩苛,卻和暖如春蹂楣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讯蒲。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工痊土, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人墨林。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓赁酝,卻偏偏與公主長得像犯祠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子酌呆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容