设计模式系列教程—Decorator Pattern(装饰者模式)

3 Decorator Pattern(装饰者模式)

3.1设计原则一类应该对扩展开放,对修改关闭

前言:装饰者模式主要是为了解决继承滥用的问题,以下将使用对象组合的方式做到在运行时装饰类。
1)案例分析一:
REQ1:星星吧咖啡店咖啡种类扩展飞快,Vander作为其老板,准备尽快更新订单系统来满足这一发展。原先的设计如下:


image.png

分析:随着饮品的发展,每种饮料都可以自由搭配,而且本身饮料也很多,除了咖啡之外,鸳鸯、奶茶、可乐、雪碧、酸奶、豆浆等等,而且调料还可以自由选择。使用不同的调料需要付不同的价格,例如一小份牛奶加入咖啡中,加收1块钱等等。如果继续按照上述的设计继续,则继承Beverage抽象类的饮料将非常多,例如coffeewithonemilk,milkTeaWithSteamAndSoy等等,这样能产生无数的搭配,并且牛奶价格上升之后,每个涉及到牛奶的类的cost函数还需要修改,这简直就是噩梦。
解决方法1:Vander 就开始设计了


image.png

超类cost()将计算所有调料的价格,子类覆盖的cost()方法扩展超类的功能,把指定的饮料类型的价钱也加上。

public class Beverage {

    private String desc;
    
    private boolean milk;
    
    private boolean soy;
    
    private boolean mocha;
    
    private boolean whip;

    public boolean hasMilk() {
        return this.milk;
    }
    
    public boolean hasSoy() {
        return this.soy;
    }
    
    public boolean hasMocha() {
        return this.mocha;
    }
    
    public boolean hasWhip() {
        return this.whip;
    }
    
    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
    
    public void setMilk(boolean milk) {
        this.milk = milk;
    }

    public void setSoy(boolean soy) {
        this.soy = soy;
    }

    public void setMocha(boolean mocha) {
        this.mocha = mocha;
    }

    public void setWhip(boolean whip) {
        this.whip = whip;
    }

    public float cost() {
        float flavourCost = 0.0f;
        if(hasMilk()) {
            flavourCost = flavourCost + 1.0f;
        } 
        if(hasSoy()) {
            flavourCost = flavourCost + 2.0f;
        }
        if(hasMocha()) {
            flavourCost = flavourCost + 3.0f;
        }
        if(hasWhip()) {
            flavourCost = flavourCost + 4.0f;
        }
        return flavourCost;
    }
    
}
public class DarkRoast extends Beverage {

    private float cost;
    
    public float getCost() {
        return cost + super.cost();
    }

    public void setCost(float cost) {
        this.cost = cost;
    }
    
}

存在的问题:
这个方法出现了以下4个问题:
1、当调料价格改变的时候需要修改Beverage类的代码。
2、一旦有新的调料,需要加上新的方法,并改变超类中的cost()方法。
3、如果有新的饮料(Tea),对这些饮料而言,某些调料(如soy、stream等)可能并不适合,但是这个设计方式中,Tea子类仍将继承那些不合适的方法,例如:hasSoy()(加入豆浆)
4、顾客万一不只是要一份摩卡,想加入两份摩卡调料,上述的方法根本无法应对。

解决方法3(使用装饰者模式)
REQ2:首先有这么一种需求,顾客买了一杯DarkRoast,想加Mocha,然后再加奶泡Whip,最后要计算这杯DarkRoast的金额。
装饰者模式可以用下面的图来说明,该图上可以看到Whip包裹着Mocha,而Mocha又包裹了DarkRoast,并且这三个类的基类都是Beverage,DarkRoast继承自Beverage,且有一个用来计算费用的cost()方法,Mocha对象是一个装饰者,它的类型反映了它所装饰的对象;Whip也是一个装饰者,所以它也反映了DarkRoast类型,并包括一个cost()方法。
最后到结账的时候,先调用最外层的Whip,得到了Whip的价格,然后再调用Mocha的cost,此时Whip的价格传给了Mocha,这样Mocha再加上自己的价格,现在就得到了Mocha+Whip的价格,然后再调用DarkRoast的价格,最后就得到了这杯咖啡的价格。这样相当于就做到“在运行时决定类的行为”。

image.png

image.png

这里要说明的是,Beverage可以用接口也可以用抽象类,若需要加入属性的话,就使用抽象类,若不需要属性则可以使用接口,方便日后代码可以extends其他的类。
以下是关键代码:

Beverage beverage = new DarkRoast();
beverage = new Whip(beverage);
beverage = new Mocha(beverage);
public class Mocha implements CondimentDecorator {

    private Beverage beverage;
    
    public float cost() {
        return this.beverage.cost() + 1.0f;
    }
    
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDesc() {
        return this.beverage.getDesc() + " with " + "Mocha";
    }

}

REQ3:那么问题来了,现实的Java世界中有哪些用了装饰者模式呢,你是否看过这样的一句new LineNumberInputStream(new BufferedInputStream(new FileInputStream)),这句话看似很复杂,其实这就是典型的装饰者模式,FileInputStream是被装饰的“组件”,Java I/O程序库提供了几个组件,包括了FileInputStream、StringBufferInputStream、ByteArrayInputStream… …等。这些类都提供了最基本的字节读取功能。
BfferedInputStream是具体的装饰者,加入了两种行为,利用缓存输入来改进性能,用readline()方法(用来一次读取一行文本输入数据)来增强接口。
LineNumberInputStream也是一个具体的装饰者,它加上了计算行数的功能。
下面进行一个小练习,写一个IO装饰者来讲输入流中所有的大写字母转成小写。

public class LowcaseInputStream extends FilterInputStream {

   public LowcaseInputStream(InputStream in) {
       super(in);
   }
   
   public int read() throws IOException {
       int c = super.read();
       return ( c == -1? c : Character.toLowerCase((char)c));
   }
   
   public int read(byte[] b, int offset, int len) throws IOException {
       int result = super.read(b, offset, len);
       for(int i = offset; i < offset + result; i++) {
           b[i] = (byte)Character.toLowerCase((char)b[i]);
       }
       return result;
   }

}
public class Main {

    public static void main(String[] args) {
        InputStream inputStream;
        int c;
        try {
            inputStream = new FileInputStream("test.txt");
            inputStream = new BufferedInputStream(inputStream);
            inputStream = new LowcaseInputStream(inputStream);
            while((c = inputStream.read()) >= 0) {
                System.out.print((char)c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
}

装饰者模式的缺点:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能造成使用此API程序员的困扰。但是其实了解了装饰者的原理,就可以容易地辨别出他们的装饰者类是如何组织的,以方便用包装方式取得想要的功能。

面向对象基础

抽象、封装、多态、继承

四大原则

设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程。
设计原则三:多用组合,少用继承。
设计原则四:为交互对象之间的松耦合设计而努力。
设计原则五:对扩展开放,对修改关闭。

模式

装饰者模式:动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。

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

推荐阅读更多精彩内容