设计模式之装饰者模式

来源:互联网 发布:纵断面图绘制软件 编辑:程序博客网 时间:2024/05/07 18:05

设计模式之装饰者模式


背景

以去咖啡馆点咖啡为例子,假设一个咖啡馆可以提供Espresso、HouseBlend、Decaf、DarkRoast这四种咖啡的类型,同时对于这每种咖啡还提供Milk、Mocha、Soy、Whip这四种调料,即每种咖啡都可以组合这失踪调味中的任意几种。

假设给这个咖啡馆设计一个点餐系统,根据用户的请求,得出最后的价格和咖啡的各种配料信息。首先根据我们直观的思路就是,根据用户的请求组合出相应的订单信息以及最后价格信息,其大概的类图可以看做下面这样:
这里写图片描述

注意:上面我还只是添加了很小的一部分,想象一下,咖啡类型的数量和调料的数量进行组合,简直就是类爆炸,可以看出来,这样的设计师非常不合理的,除去扩展性很差之外,当咖啡种类和调料种类数量在不断增加的时候,最后类的数量简直就是爆炸式增长。

那么有没有更好一点的设计方案呢?

另一种思路:
假设在Beverage类中为每一个调料设计一个控制器,用来控制是否加入该调料,然后在getCost方法中根据判断哪些调料加入了,从而获取相应的价钱,最后得到总价钱。其类图大概的样子如下所示:
这里写图片描述

说明:通过相应的调料控制器来控制是否加入了该调料,然后获取相应的调料的价格然后累加算作最后的总价格返回给客户。

这似乎看上去可行,不错!确实可行,但是却违背了之前我们讲到的几个设计原则。

首先,每当调料的价格发生了变化的时候(而且我认为这似乎是很常见的情况),我们都需要修改代码。

然后,如果咖啡店新增加了一个新的咖啡品种或者新的咖啡调料,哪的修改超类Beverage中的代码,还需要更改getCost()方法。

接着,假设新出现了一个新的饮料A,在这个新的饮料A中是不会出现调料milk,那么对于饮料A来说调料milk是不需要的,而且是浪费的,以此类推,假设饮品的种类越来越多,在超类Beverage中添加的控制器也会越来越多,但是对于每一类饮品来说,可用的或者需要用到的调料控制器仅仅是其中的几个,这样的话就会造成很多的冗余和资源浪费。

还有,继续思考,我们知道,假如加入了milk,这milk的控制器就会为true,表示当前咖啡中加入了milk,好,这没问题!但是如果我想点一份加了两份milk的咖啡了?如何去表示两份?在计算价格的时候似乎也是一件麻烦事,因为有可能加了三份、四份。。。。

思考:类比Angular中的管道,对于每一种调料,每当需要这种调料的时候,就调用一下这个调料,需要几个就调用几个,需要不同种类的调料就调用不同种类的调料。

由此得到了一个新的设计原则:

类应该对扩展开放,对修改关闭—- 开放-关闭原则

装饰者模式

认识装饰者模式

接着上面的背景,然后根据之前的分析思路:
首先,点一杯Espresso咖啡
然后,我需要加点Milk,于是用Milk来装饰它,此时就是Espresso+Milk
接着,我还想加点Whip,于是再用Whip来装饰它,即Espresso+Milk+Whip
继续,我发现我好想还想再来点Milk,继续使用Milk装饰,则:Espresso+Milk+Whip+Milk
最后,需要结账,调用getCost方法返回最后的价钱。

其中最值得注意的就是计算价格上,是通过什么方式委托每一种调料计算价格的呢?
计算模型大概如下所示:
这里写图片描述

装饰者模式定义

装饰者模式 动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

通过上面的分析,我们现在对装饰者模式的了解大概如下:
1、装饰者和被装饰者应该是继承自同一个超类,即两者有相同的类型。因为被装饰者在被装饰之后任然属于被装饰者,这就要求两者的类型应该统一。
2、一个对象可以使用一个或者多个装饰者来进行装饰,而且是不限制数量的,并且可以在程序运行的任何时间。
3、装饰者可以在对某个对象进行装饰之前或者之后进行一些自己的相关操作,从而达到一些特定场景的需求。

根据上面对装饰者模式的了解,可以得到如下的简要的类图:
这里写图片描述

说明:
1、每个装饰者中都有一个超类的变量,用来保存当前这个装饰者所要装饰的对象。
2、在这些具体的装饰者中,每个装饰者可以新增加自己的状态和方法以扩展超类。

代码实现

根据上面的有关装饰者的具体分析中,我们已经基本掌握了装饰者模式的特点,以及使用装饰者模式应该具体怎么设计。接下来,就根据之前背景信息中所讲到的咖啡馆的案例,并且结合代码来实现这样的一个咖啡点餐系统。

系统的类图设计

结合上述的装饰者的简要的系统类图可以得到该咖啡点餐系统的类图设计:
这里写图片描述

注意:这些调料的装饰类除了实现getCost方法之外,还必须实现getDescription()方法,原因是超类中的getDescription 虽然不是抽象方法,但是在CondimentDevcorator中再次定义了一个抽象的getDescription方法。所以对于具体调料的实现来说,必须实现getCost和getDescription两个方法。

Beverage超类的实现:

package com.huaxin;public abstract class Beverage {    private String description = "Unknown Beverage";    public String getDescription() {        return description;    }    public abstract double getCost();}

说明:这个类是所有咖啡品种和调料品种的超类,其中定义了一个大家公共的属性description,用来介绍相应的咖啡或者调料。还有一个公共的getDescription的方法来返回这description,另外还有一个抽象的获取价格的方法,让其子类进行实现。

CondimentDecorator抽象类的实现:

package com.huaxin.condiment;import com.huaxin.Beverage;public abstract class CondimentDecorator extends Beverage {    public abstract String getDescription();}

说明:仅仅是在继承了超类之后增加了一个抽象的getDescription 方法,从而使得所有的调料子类都必须实现这个getDescription和来自超类的getCost方法。

几种咖啡的具体实现

HouseBlend的实现:

package com.huaxin.drink;import com.huaxin.Beverage;public class HouseBlend extends Beverage {    public HouseBlend (){        description = "HouseBlend";    }    public double getCost() {        return 0.98;    }}

说明:将有超类继承而来的description属性赋值为相应的咖啡的名字“HouseBlend”,同时实现由超类继承而来的getCost方法,直接返回该咖啡的商品价格。以下几种咖啡的实现说明与此一样,不在赘述。

Espresso的实现:

package com.huaxin.drink;import com.huaxin.Beverage;public class Espresso extends Beverage {    public Espresso (){        description = "Espresso";    }    public double getCost() {        return 1.23;    }}

Decaf的实现:

package com.huaxin.drink;import com.huaxin.Beverage;public class Decaf extends Beverage {    public Decaf (){        description = "Decaf";    }    public double getCost() {        return 1.09;    }}

DarkRoast 的实现:

package com.huaxin.drink;import com.huaxin.Beverage;public class DarkRoast extends Beverage {    public DarkRoast (){        description = "Dark Roast";    }    public double getCost() {        return 2.10;    }}

几种调料的具体实现

Milk调料的实现:

package com.huaxin.condiment;import com.huaxin.Beverage;public class Milk extends CondimentDecorator {    Beverage beverage;    public Milk (Beverage beverage){        this.beverage = beverage;    }    public String getDescription() {        return beverage.getDescription()+" ,with Milk";    }    public double getCost() {        return beverage.getCost()+.56;    }}

说明:
1、首先,定义了一个超类类型的变量beverage用来存储当前需要进行装饰的对象。
2、然后,通过Milk的构造器将需要装饰的对象传递进来并且通过构造器将其赋值给贝蒂变量beverage中。
3、此处的getDescription是实现了其父类CondimentDecorator中的getDescription方法,在该方法中调用了需要被装饰对象的getDescription方法,从而返回了该被装饰对象的介绍信息。
4、在返回价格的时候首先获取当前需要被装饰对象的价格,在此基础上只需要加上该调料的价格就是该咖啡加上该调料之后的总价格(似乎跟递归调用的思想是一样的)。

下面的几个调料的具体实现的说明跟上述说明一样,不在赘述。

Mocha调料的具体实现:

package com.huaxin.condiment;import com.huaxin.Beverage;public class Mocha extends CondimentDecorator {    Beverage beverage;    public Mocha(Beverage beverage){        this.beverage = beverage;    }    public String getDescription() {        return beverage.getDescription()+" ,with Mocha";    }    public double getCost() {        return beverage.getCost()+.45;    }}

Soy调料的具体实现:

package com.huaxin.condiment;import com.huaxin.Beverage;public class Soy extends CondimentDecorator {    Beverage beverage;    public Soy(Beverage beverage){        this.beverage = beverage;    }    public String getDescription() {        return beverage.getDescription()+" ,with Soy";    }    public double getCost() {        return beverage.getCost()+.98;    }}

Whip调料的具体实现:

package com.huaxin.condiment;import com.huaxin.Beverage;public class Whip extends CondimentDecorator {    Beverage beverage;    public Whip(Beverage beverage) {        this.beverage=beverage;    }    public String getDescription() {        return beverage.getDescription()+" ,with Whip";    }    public double getCost() {        return beverage.getCost()+1.24;    }}

以上便实现了咖啡点餐系统的基本功能了,根据装饰者模式,可以灵活的根据用户的不同点餐需求得到相应的订单信息和价格。一下通过一个测试实例来直观感受一下。

系统测试

通过Main函数的测试代码如下:

import com.huaxin.Beverage;import com.huaxin.condiment.Milk;import com.huaxin.condiment.Mocha;import com.huaxin.condiment.Soy;import com.huaxin.condiment.Whip;import com.huaxin.drink.Espresso;import com.huaxin.drink.HouseBlend;public class TestMain {    public static void main(String[] args) {        Beverage beverage1 = new Espresso();        System.out.println(beverage1.getDescription()+ "  $"+beverage1.getCost());        Beverage beverage = new HouseBlend();        beverage = new Milk(beverage);        System.out.println(beverage.getDescription()+ "  $"+beverage.getCost());        beverage = new Soy(beverage);        System.out.println(beverage.getDescription()+ "  $"+beverage.getCost());        beverage = new Mocha(beverage);        System.out.println(beverage.getDescription()+ "  $"+beverage.getCost());        beverage = new Whip(beverage);        System.out.println(beverage.getDescription()+ "  $"+beverage.getCost());    }}

根据上面的Main函数的运行结果如下:
这里写图片描述

通过以上的结果可以很清楚的看到运行的结果已经达到了我们预期的效果。

以上便是整个装饰者设计模式的学习内容,以上的代码实测可以直接运行。

原创粉丝点击