设计模式:第三篇--装饰者模式

装饰者模式简介

定义:装饰者模式动态的将责任附加到对象上,若要拓展功能,装饰者提供了比继承更有弹性的方案,装饰者可以在被装饰者行为前后加上自己的行为以达到特定的目的。

从UML图上理解呢,就是有一个抽象的组件,定义了抽象的方法,由于拓展的需要,抽象了一个装饰类,继承这个类的所有类都是装饰了原有组件之后的组件。这里需要注意的是,Decorator和Component是组合关系,脱离了Component就不复存在了,说到底,被拓展的类还是原来的组件,只不过增加了一些东西。由于组合的依赖甚,装饰类他们本身是可以代表组件本身的。原则就是在原有的基础上增加新的东西,无论是功能还是属性都可以,但是无法变成脱离原来东西以外的新东西。

案例:星巴克

需求:本店主营是做各种咖啡以及其他饮料的。不管怎样,这家都都是做饮料的。客人越来越多了,现在我们要用开发一个收款的程序,来管理所有的品类,代替人工计算商品价格,方便店员收款。

1、小白设计:单纯继承

思考:既然都是饮料,那么抽象一个饮料父类。以后每个单品继承该父类,并再子类计算价格即可。于是设计的如下。

这个设计满足了一段时间的使用,但是问题来了,由于客人越来越多,店面也有扩张,从原来的4个单品扩张为100个单品,并且还有提供了各种调味料调味,加糖的,加盐的,加奶的等等。突然发现这种组合太恐怖了,那我要成百上千的类啊。而且随着后续组合的增加,每增加一个品种,其组合都是爆炸式的增长。而且这还不是最麻烦的,计算价钱怎么办?得每一种组合里面自己算。可是调味料和品类就这么多。而我要算很多次。太恐怖了。

总结:类爆炸,单纯的继承对于组合拓展来说,简直式噩梦,会导致类爆炸。

2、使用装饰者模式

思考:思考一下需求,饮料需要加调味料,饮料的种类和调味料的种类都式可以拓展的。我们之前学习的策略模式和观察者模式好像都不太够用啊。如果用策略模式来做,那么每一种组合就是一种策略,也会有很多类。能参考的只有之前学的设计原则,多用组合了,这个需求本身就是组合,查阅资料,发现装饰者模式,激动万分。改造一下。

饮料的四种单品:DarkRoast、Decaf、Espresso、HouseBlend 都是咖啡

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:00.
 *
 * 饮料
 */
public abstract class Beverage {

    protected String discription = "unknown beverage";

    public String getDiscription() {
        return discription;
    }

    /**
     * 计算价格
     * @return
     */
    public abstract double cost();

}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:03.
 * 
 * 单品:DarkRoast
 */
public class DarkRoast extends Beverage {

    public DarkRoast() {
        discription = "DarkRoast";
    }

    @Override
    public double cost() {
        return .99;
    }
}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:03.
 * 
 * 单品:Decaf
 */
public class Decaf extends Beverage {

    public Decaf() {
        discription = "Decaf";
    }

    @Override
    public double cost() {
        return 2.33;
    }
}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:03.
 * 
 * 单品:Espresso
 */
public class Espresso extends Beverage {

    public Espresso() {
        discription = "Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:03.
 * 
 * 单品:HouseBlend
 */
public class HouseBlend extends Beverage {
    public HouseBlend() {
        discription = "HouseBlend";
    }

    @Override
    public double cost() {

        return .89;
    }
}

调料的四种种类:Milk、Mocha、Soy、Whip
备注:个人理解,这里定义的其实是用调料装饰单品后的东西。因此以下的类并不是单纯指调料。只是我们使用的时候方便理解这么命名了。

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:04.
 * <p>
 * 调料装饰者
 */
public abstract class CondimentDecorator extends Beverage {

    Beverage beverage;

    @Override
    public abstract String getDiscription();
}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:06.
 * 
 * 被Milk装饰
 */
public class Milk extends CondimentDecorator {


    public Milk(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDiscription() {
        return beverage.getDiscription() + ", Milk";
    }

    @Override
    public double cost() {

        return beverage.cost() + .23;
    }
}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:06.
 * 
 * 被Mocha装饰
 */
public class Mocha extends CondimentDecorator {


    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDiscription() {
        return beverage.getDiscription() + ", Mocha";
    }

    @Override
    public double cost() {

        return beverage.cost() + .20;
    }
}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:06.
 * 
 * 被Soy装饰
 */
public class Soy extends CondimentDecorator {


    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDiscription() {
        return beverage.getDiscription() + ", Soy";
    }

    @Override
    public double cost() {

        return beverage.cost() + 0.44;
    }
}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 16:06.
 * 
 * 被Whip装饰
 */
public class Whip extends CondimentDecorator {


    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }


    @Override
    public String getDiscription() {
        return beverage.getDiscription() + ", Whip";
    }

    @Override
    public double cost() {

        return 0.56 + beverage.cost();
    }
}

测试一下:

public class Test {


    public static void main(String[] args) {

        //点一杯Espresso
        Beverage espresso = new Espresso();
        System.out.println(espresso.getDiscription());
        System.out.println(espresso.cost());

        //点一杯DarkRoast,加两份Mocha,加一份Whip
        Beverage darkRoast = new DarkRoast();
        darkRoast = new Mocha(darkRoast);
        darkRoast = new Mocha(darkRoast);
        darkRoast = new Whip(darkRoast);
        System.out.println(darkRoast.getDiscription());
        System.out.println(darkRoast.cost());

        //来一杯调料为:豆浆,摩卡,奶泡的HouseBlend咖啡
        Beverage houseBlend = new HouseBlend();
        houseBlend =  new Soy(houseBlend);
        houseBlend = new Mocha(houseBlend);
        houseBlend = new Whip(houseBlend);
        System.out.println(houseBlend.getDiscription());
        System.out.println(houseBlend.cost());

    }

}

总结:小编发现其实还是用的继承,只不过多了一个组合的思想。组合就应该是用组合,对象之间都是组合关系的,我们就没有理由不用组合去设计了。如果不想用组合这里的抽象类也可以写成接口。将依赖下方到具体的子类中其实是一样的。只不过由于组件和装饰者这些都是是一个个角色,使用抽象的意义貌似比接口要大一点。因为基调就是组合衍生新的组合。这么一看,貌似又点熟悉,java的I/O框架是不是也是用的这种模式,一层一层包装。

3、jdk的I/O框架

仔细观察IO框架,其实我们可以发现其设计就是经典的装饰着模式,不同的是到了java8装饰者并不是抽象的而已。其实如果理解了装饰着模式的核心之后,我们发现其核心的思想在于组合,而不是拘泥于抽象接口或者继承。所有的这些只是手段,我们要达到的是弹性组合的效果。就可以了。而装饰者只是一个角色而已。其依赖上层被装装饰的组件。知道了这一点我们甚至可以自己去定制拓展自己的IO组件了。


4、案例总结:

装饰者模式其实蛮好理解的,核心在于理解组合,弹性组合。关键代码是组合属性以及构造方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,734评论 0 14
  • 1 场景问题# 1.1 复杂的奖金计算## 考虑这样一个实际应用:就是如何实现灵活的奖金计算。 奖金计算是相对复杂...
    七寸知架构阅读 3,950评论 4 67
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,897评论 1 15
  • 体验入 每个人都有让自己过得更好理由,一定要坚持这个理由,并让他变成动力 核心 动力源泉 转生用 利用自己的动力源...
    乾立风中阅读 64评论 0 0
  • 在面对困惑时,我总会问自己:到底要怎样的人生? 我常常困在自己的框架里,有时清醒,有时模糊。清醒之时,浑身上下充满...
    70珊妮阅读 303评论 0 0