生活中的設計模式之裝飾器模式

情景

假設,不久前你在餓了么開了一家面店浑玛,只賣一種面既原味面,但生意卻很火爆噩咪。昨天顾彰,你查看店鋪的留言時發(fā)現(xiàn):有很多客戶要求增加雞蛋和番茄等配菜。
為了不流失客戶胃碾,你決定通過繼承的方式新增雞蛋面和番茄面涨享,來滿足客戶需求。擴展后的菜單如下:

avatar

新品推出后仆百,得到了部份客戶的一致好評厕隧,但也有給差評的。差評的原因大多都是因為無法組合配菜,其中一條差評留言是:無法下單兩個雞蛋的番茄面吁讨。
為了不影響店鋪的口碑髓迎,你決定再次通過繼承新增番茄雞蛋面和兩個雞蛋的番茄面。菜單如下:

avatar

問題

顯然建丧,繼承將功能(配菜)和對象(面)靜態(tài)的綁定在了一起排龄,使原本可選的功能失去了動態(tài)組合的靈活性,導致無法動態(tài)地組合已有的功能來擴展對象的功能翎朱。
如果可選的功能(配菜)很多橄维,且可以相互組合;那么通過繼承的方式會導致類膨脹拴曲,代碼重復争舞、難以維護等問題。所以澈灼,這時我們應該考慮另一種更靈活的方式——裝飾器模式竞川。

方案

avatar

裝飾器模式通過一個獨立類即裝飾器包裝對象的方式,給對象擴展新功能叁熔。
裝飾器即對象的功能流译,它和對象之間是一種組合關系而非繼承關系,且裝飾器之間可嵌套者疤。
裝飾器實現(xiàn)了和原對象一致的接口福澡,所以它可以替原對象接收客戶請求,并在轉發(fā)請求前后對其功能進行擴展驹马。

avatar

public class Client {

    public static void main(String[] args){
        //制作二個雞蛋的番茄雞蛋面

        //原味面
        OriginalNoddles noddles = OriginalNoddles();
        //第一個雞蛋面裝飾器
        EggDecorator eggDecorator = new EggDecorator(noddles);

        //第二個雞蛋面裝飾器
        EggDecorator eggDecoratorTwo = new EggDecorator(eggDecorator);

        //番茄裝飾器
        TomatoDecorator noddlesWithTwoEggAndTomato = new TomatoDecorator(eggDecoratorTwo);
    }
}

結構

avatar

抽象構件(Component):定義了具體構件和抽象裝飾器需要實現(xiàn)的接口革砸。

具體構件(ConcreteComponent):被包裝的原始對象,實現(xiàn)了Component接口糯累。

抽象裝飾器(Decorator):抽象類算利,持有一個指向被包裝對象的引用。

具體裝飾器(ConcreteDecorator):實現(xiàn)了具體的擴展功能泳姐,在被包裝的對象方法調(diào)用前后進行功能擴展效拭。

第一步:創(chuàng)建抽象構件。


public interface Component {
    public void operation();
}

第二步:創(chuàng)建具體構件胖秒。


public class ConcreteComponent implements Component{

    @Override
    public void operation() {
        System.out.println("the original operation");
    }
}

第三步:創(chuàng)建抽象裝飾器缎患,并實現(xiàn)抽象構件接口。


public abstract class Decorator implements Component{

    private final Component component;

    public Decorator(Component component){
        this.component = component;
    }

    @Override
    public void operation() {
        this.component.operation();
    }
}

第四步:創(chuàng)建具體裝飾器并繼承抽象裝飾器阎肝。


public  class ConcreteDecoratorA extends Decorator{

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        additionalFunction();
    }

    protected void additionalFunction(){
        System.out.println("this is an additional functionA ");
    }
}


第五步:使用具體裝飾器包裝具體構件挤渔。


public  class ConcreteDecoratorA extends Decorator{

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        additionalFunction();
    }

    protected void additionalFunction(){
        System.out.println("this is an additional functionA ");
    }
}


第六步:使用具體裝飾器擴展對象的功能。


public class Client2 {

    public static void main(String[] args) {
        //被裝飾者
        Component p = new ConcreteComponent();
        //使用裝飾器A包裝對象p
        Component a = new ConcreteDecoratorA(p);
        //使用裝飾器B包裝對象a
        Component b = new ConcreteDecoratorB(a);
        //執(zhí)行操作
        b.operation();
    }
}

應用

假設风题,我們有一個發(fā)送短信的服務類判导,它可以根據(jù)具體的短信通道來發(fā)送驗證碼和活動通知嫉父。短信通道我們接入了兩個,一個是騰訊短信通道眼刃,一個是阿里短信通道绕辖。短信服務類如下:


public enum Channel {
    ALI,TENXUN
}

public interface MessageService {

    public String send(Message message,Channel channel);

}

public class SMSService implements MessageService{

    @Override
    public String send(Message message, Channel channel) {
        //......
        if(Channel.ALI.equals(channel)){
            aliChannel.send(message);
        }else if(Channel.TENXUN.equals(channel)){
            tenxunChannel.send(message);
        }
        //......
        //0001表示對方服務異常
        return "0001";
    }
}

現(xiàn)在,我們希望給短信服務擴展一個功能:當阿里或騰訊的短信服務因異常收不到我們發(fā)送的短信時擂红,及時使用另一個短信通道重試一次仪际。

第一步:創(chuàng)建抽象的消息服務裝飾器MessageServiceDecorator。


public abstract class MessageServiceDecorator implements MessageService{
    private final MessageService messageService;

    public MessageServiceDecorator(MessageService messageService){
        this.messageService = messageService;
    }
    @Override
    public String send(Message message, Channel channel) {
        return messageService.send(message,channel);
    }
}

第二步:創(chuàng)建具體裝飾類重試消息服務RetryMessageService篮条。


public class RetryMessageService extends MessageServiceDecorator{

    public RetryMessageService(MessageService messageService) {
        super(messageService);
    }

    @Override
    public String send(Message message, Channel channel) {
        String result = super.send(message, channel);
        //當前短信通道異常,切換另一個通道
        if("0001".equals(result)){
             Channel anotherChannel = Channel.ALI;
             if(anotherChannel==channel){
                 anotherChannel=Channel.TENXUN;
             }
             return super.send(message, anotherChannel);
        }
        return result;
    }
}

第三步:客戶類使用帶重試功能的短信服務發(fā)送短信吩抓,使用沒有被裝飾的短信服務發(fā)送營銷活動短信涉茧。


public class Client {

    public static void main(String[] args){

        SMSService smsService = new SMSService();
        //發(fā)送營銷短信
        smsService.send(new Message(),Channel.ALI);

        //用重試裝飾器包裝短信服務對象
        RetryMessageService retryMessageService = new RetryMessageService(smsService);
        retryMessageService.send(new Message(),Channel.ALI);
    }
}

通過裝飾器我們就給短信服務添加了一個可選的擴展功能"切換通道重試",其它的功能如黑名單疹娶、頻次控制等都可以用同樣的方式進行擴展伴栓。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雨饺,隨后出現(xiàn)的幾起案子钳垮,更是在濱河造成了極大的恐慌,老刑警劉巖额港,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饺窿,死亡現(xiàn)場離奇詭異,居然都是意外死亡移斩,警方通過查閱死者的電腦和手機肚医,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來向瓷,“玉大人肠套,你說我怎么就攤上這事〔危” “怎么了你稚?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朱躺。 經(jīng)常有香客問我刁赖,道長,這世上最難降的妖魔是什么长搀? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任乾闰,我火速辦了婚禮,結果婚禮上盈滴,老公的妹妹穿的比我還像新娘涯肩。我一直安慰自己轿钠,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布病苗。 她就那樣靜靜地躺著疗垛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪硫朦。 梳的紋絲不亂的頭發(fā)上贷腕,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音咬展,去河邊找鬼泽裳。 笑死,一個胖子當著我的面吹牛破婆,可吹牛的內(nèi)容都是我干的涮总。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼祷舀,長吁一口氣:“原來是場噩夢啊……” “哼瀑梗!你這毒婦竟也來了?” 一聲冷哼從身側響起裳扯,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抛丽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饰豺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亿鲜,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年冤吨,在試婚紗的時候發(fā)現(xiàn)自己被綠了狡门。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡锅很,死狀恐怖其馏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爆安,我是刑警寧澤叛复,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站扔仓,受9級特大地震影響褐奥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翘簇,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一撬码、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧版保,春花似錦呜笑、人聲如沸夫否。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凰慈。三九已至,卻和暖如春驼鹅,著一層夾襖步出監(jiān)牢的瞬間微谓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工输钩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留豺型,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓买乃,卻偏偏與公主長得像姻氨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子为牍,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

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