装饰器模式(从放弃到入门)

前面介绍了两篇设计模式,策略模式和观察者模式,其实自己也是在学习阶段,感觉收益很大。所以想继续分享,把java中的23中设计模式都总结一遍,在以后才能在实践中灵活运用。感兴趣的童鞋可以看看前面分享的两篇:

策略模式
观察者模式

前面两篇都是上来就是例子,需求,我想改变一下套路,今天先介绍装饰器的理论结构,再说例子。还是要再声明:例子来自于《HeadFirst 设计模式》,推荐大家看看原书,写得很浅显易懂。

理论知识

6.png

实际问题

今天的例子是一个 coffe 的例子,相信大家都去星巴克喝过咖啡,或者奶茶,我们在买coffe的时候,首先选择一个基本的coffe类型,比如 卡布奇洛,然后添加各种佐料:摩卡,豆浆,蒸奶等。基本类型是基础价,佐料又要宁外算钱。(真是聪明)

现在当前的系统设计是:

1.png

本来想自己画UML图,发现自己画也一样,还损失了书上一些重要信息,所以直接盗图好了,大家不要介意。
Beverage: 饮料,抽象类,getDescription() 返回描述(类型,佐料...),cost() 抽象方法,每种饮料话费不一样,所以子类自己去实现。
然后派生了4中饮料的子类: HoseBlend, DarkRoast, Decaf, Espresso(尼玛,我都没喝过),分别实现 cost方法,返回价格。

代码很简单,这里就不贴了,然后这个时候,如果饮料有100种,呵呵,这种设计类图就是这样:

2.png

类爆炸!这种设计,明显重用率太低,好吧,换种思路,我们把所有的佐料的放到公共父类中,让所有子类都拥有所有的佐料,只是在类中判断到底加没加,例如这样:

3.png

代码:

Beverage .java

public abstract class Beverage {
    protected String description;
    
    protected boolean milk;
    protected boolean soy;
    protected boolean mocha;
    protected boolean whip;
    
    public Beverage(){
        this.description = "unknown beverage";
    }

    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}

HoseBlend.java

public class HoseBlend extends Beverage {

    public HoseBlend() {
        description = "HoseBlend";

        milk = true;
        soy = false;
        mocha = false;
        whip = false;
    }

    public double cost() {
        double money = 10.0;
        if (milk)
            money += 2.0;
        if (soy)
            money += 3.0;
        if (mocha)
            money += 3.0;
        if (whip)
            money += 2.0;
        return money;
    }
}

使用:

Beverage hoseBlend = new HoseBlend();
System.out.println(hoseBlend.getDescription());
System.out.println(hoseBlend.cost());

其他类省略了,当然这里写得不规范,每种佐料的价格应该写成常量,这里直接用了数字,这里不是重点。这样的类设计出来,我们可以用一段话来描述:一个抽象的Beverage类,里面包含了很多佐料,子类决定是否添加这些佐料,并且计算初始价格和佐料价格。

与前一种不同的是,这种更加规范,父类决定了所有的佐料和价格,只是你填不填加,自己决定。突然想到了一种更好的方法,为何不让Beverage去计算价格呢?

Beverage.java

public abstract class Beverage {
    protected String description;
    protected double money;
    
    public Beverage(){
        this.description = "unknown beverage";
        this.money = 10.0;
    }

    public String getDescription() {
        return description;
    }
    
    public double cost(){
        return money;
    }

    public void addMilk() {
        this.money += 2.0;
    }
    
    public void addSoy(){
        this.money += 3.0;
    }
    
    public void addMocha(){
        this.money += 3.0;
    }
    
    public void addWhip(){
        this.money += 2.0;
    }
}

添加4中 addXXX() 方法,然后计算money,将计算价格留给父类,子类只需要添加佐料:

HoseBlend .java

public class HoseBlend extends Beverage {
    public HoseBlend() {
        description = "HoseBlend";
        addMilk();
    }
}

感觉这样写重用可以更好,并且子类工作也更少了。呵呵,书上没写,自己瞎想的。但是上面的这种设计,当遇到添加一种佐料时,都必须修改Beverate类,在设计模式原则中有一个非常重要的原则,就是开闭原则:对扩展开放,对修改关闭。上面的几种设计都存在一定的问题。我们来看看今天的主角,装饰器模式,怎么来完成。

装饰器模式

先来看一张形象的图:

4.png

最里层的 DarkRoast 是饮料类型外面添加一层 Mocha, 再外层添加 Whip。最终cost() 就从最外层,一直调用到最里层,累加得到价格,呵呵,是不是有点像递归,对多态的递归,看看类结构:

5.png

当然这是在最初,我们类爆炸那个例子中的结构扩展的:
Beverage依然是那个抽象类,依然有4种饮料继承与它。不同的是,多了一个 CondimentDecorator,继承于 Beverage,然后4种佐料都继承与 CondimentDecorator,并都包含一个 beverage 的引用,表示自己装饰的对象。

好了,看看代码:

Beverage .java 还是长这样

public abstract class Beverage {
    protected String description;
    
    public Beverage(){
        this.description = "unknown beverage";
    }

    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}

HouseBlend .java 第一种饮料,最低10.0元

public class HouseBlend extends Beverage {
    
    public HouseBlend(){
        description = "HoseBlend";
    }

    public double cost() {
        return 10.0;
    }
}

DarkRoast.java 第二种饮料,最低12.0元

public class DarkRoast extends Beverage {
    
    public DarkRoast(){
        description = "DarkRoast";
    }

    public double cost() {
        return 12.0;
    }
}

CondimentDecorator .java 让子类都重写getDescription() 方法

public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

Milk.java : 牛奶,每加一份2.0 元

public class Milk extends CondimentDecorator {

    Beverage beverage;

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

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Milk";
    }

    public double cost() {
        return this.beverage.cost() + 2.0;
    }
}

Mocha.java 摩卡,每份3.0元

public class Mocha extends CondimentDecorator {

    Beverage beverage;

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

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Mocha";
    }

    public double cost() {
        return this.beverage.cost() + 3.0;
    }
}

Soy.java 酱油,每份3.0元

public class Soy extends CondimentDecorator {

    Beverage beverage;

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

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Soy";
    }

    public double cost() {
        return this.beverage.cost() + 3.0;
    }
}

好了,看看我们怎么调用:

public class Main {
    public static void main(String[] args) {
        Beverage b1 = new HouseBlend();
        System.out.println(b1.getDescription() + " $" + b1.cost());
        
        Beverage b2 = new DarkRoast();
        b2 = new Mocha(b2);
        b2 = new Soy(b2);
        b2 = new Milk(b2);
        System.out.println(b2.getDescription() + " $" + b2.cost());
    }
}

输出:

HoseBlend $10.0
DarkRoast,Mocha,Soy,Milk $20.0

总结

  1. 解耦合了吧,饮料和佐料分开,想怎么加怎么加,如果要添加新饮料或者佐料,只需继续添加,而无需修改以前的结构。这就是对扩展开放,对修改关闭
  2. 我前面提到了一句话:递归多态,哈哈哈,自己瞎编的!为什么会出现这种结果,如果你对多态熟悉的话,就很好理解了,看上面代码的 b2:
  • 为什么 Milk 可以赋值给 Beverage , 因为 Milk 的父类继承于 Beverage
  • b2调用 cost() 是调用 Milk 的 cost() = 2.0+ this.beverage.cost(), this.beverage指向的是 上一个b2,及 Soy, Soy调用cost(), 及调用 Mocha.cost() + 3.0 ... , 知道最后调用 beverage 的 cost() , 是不是很像递归。

再一句话概括装饰者模式吧:

装饰者模式,就是装饰者(Docorator)需要继承与被装饰者(Component),并且持有被装饰者的引用(Component),从而可以通过复写,在原来 Component 的基础上对方法做一定的修饰。

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

推荐阅读更多精彩内容