设计模式——装饰者模式
Posted 0x3f3f3f3f
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式——装饰者模式相关的知识,希望对你有一定的参考价值。
案例引入装饰者模式
假设我们现在要实现一个咖啡管理系统,来计算不同咖啡的价格,和管理对每一种咖啡的描述
如上类图所示,$Beverage$是一个饮料的抽象类,该店所有饮料商品都应该继承这个类
$description$变量是子类自己定义的,用来描述当前饮料的。该描述通过$getDescription()$返回。
$cost()$方法是抽象方法,是用来计算饮料的价格,由每个子类自己实现。
而,在我们实际需求中,购买者可能根据个人口味要求在咖啡中添加各种调料,如奶,糖, 巧克力等等。而加的调料不同就会导致最后咖啡的价格不同,所以订单系统必须要考虑到这些调料的部分。按照上一个类图的思路,我们不难想到,把每一种添加调料的咖啡作为一个子类来实现$cost()$方法,这样没多一种调料就会多很多的排列组合情况,结果如下图所示。这就是类爆炸的情况发生,显然是不可取的。
为了解决上述类爆炸的情况,我们首先想到的解决方案是,在父类定义若干代表是否添加某调料的变量,然后再父类实现$cost()$方法,里边只需要判断代表某调料的变量值是否为$true$,为$true$则计算上该调料的价格,计算完成后,返回计算出来的价格即可,而子类只需要调用父类的$cost()$方法,即可获取价格。
如下图类图所示
类中的$hasXxx()$和$setXxx()$方法是用来判断是否有某调料和设置该调料的方法。
我们知道,面向对象设计原则里有很重要的一条是对修改关闭, 对扩展开放
而如果按照上述方式设计类的话,难免会产生如下的问题。
- 如果调料的价格改变,则需要修改代码
- 如果出现新调料,就要加新的$hasXxx()$和$setXxx()$方法,并且要更改$cost()$方法
- 如果某饮料不适合该加该调料,但是继承的时候,加该调料的方法会被一并继承
- 如果某顾客想要加双倍的某调料,或者三倍的某调料则无法实现
因此,我们可以得出结论,这种设计方式也是不可取的。
为了解决这个场景,我们来使用装饰者模式
定义装饰者模式
装饰这模式动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者模式来解决上述问题
我们以饮料为主体,然后运行时以调料来装饰饮料
例如:顾客想要摩卡和奶泡深焙咖啡,那么要做的是:
- 拿一个深焙咖啡$(DarkRoast)$对象
- 以摩卡$(Mocha)$对象装饰它
- 以奶泡$(Whip)$对象装饰它
- 调用$cost()$方法,并依赖委托将调料价格加上去
以装饰者构造饮料订单
- 以$DarkRoast$对象开始
- 建立一个$Mocha$对象,并用它将$DarkRoast$对象包起来
$Mocha$对象是一个装饰者,他与所装饰对象的父类类型一致,因此它里边也有$cost()$方法。 - 顾客也想要奶泡,所以需要建立一个Whip装饰者,并用它将$Mocha$对象包起来
$Whip$是一个装饰者,他与所装饰的对象类型的父类类型一致,因此它也包括一个$cost()$方法 - 所以在计算价格时,通过调用最外圈的装饰者的$cost()$方法就可以得到结果。
所以实际调用流程是,最外层装饰者通过自身和内层调用结果算出总价格,内层调用者通过自身和被装饰者的调用结果算出本层的总价格,然后被装饰者调用自身方法返回价格。这样以一个递归的形式完成调用。我们可以得到的结论
- 装饰者和被装饰者有相同的父类类型
- 你可以用一个或多个装饰者包装一个对象
- 既然装饰者和被装饰者对象有相同的父类类型,所以在任何需要原始对象的场合,可以用装饰过他的对象代替。
- 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
- 对象可以在任何时候被修饰,所以可以运行时动态的,不限量的使用你喜欢的装饰者来装饰对象。
我们总结上述有关装饰者模式的一些内容,可以得到一些代码结构
在该设计模式里,一共涉及到了四种角色
- 抽象组件:定义一个抽象接口,来规范准备附加功能的类
- 具体组件:将要被附加功能的类,实现抽象构件角色接口
- 抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一致的接口
- 具体装饰:实现抽象装饰者角色,负责为具体构件添加额外功能
对于饮料来说的装饰者模式
其中的$Beverage$相当于抽象组件,而左侧的咖啡则是具体的组件。$CondimentDecorator$则是抽象装饰者,而底下的调料则是具体装饰。
实现装饰者模式
/**
* 饮料类:抽象组件
*/
public abstract class Beverage
String description = "UnKown Beverage";
public String getDescription()
return description;
public abstract double cost();
/**
* 调料类:抽象装饰者类
*/
public abstract class CondimentDecorator extends Beverage
public abstract String getDescription();//所有的调料装饰者都必须实现这个方法
/**
* 浓缩咖啡类:具体组件
*/
public class Espresso extends Beverage
public Espresso()
description = "Espresso";//该变量继承自Beverage
@Override
public double cost()
return 1.99;
/**
* 综合咖啡类:具体组件
*/
public class HouseBlend extends Beverage
public HouseBlend()
description = "HouseBlend";
@Override
public double cost()
return 0.89;
/**
* 摩卡调料:具体装饰者
*/
public class Mocha extends CondimentDecorator
Beverage beverage;
public Mocha(Beverage beverage)
this.beverage = beverage;//用一个实例变量记录饮料,也就是被装饰者
@Override
public double cost()
return beverage.cost() + 0.2;//首先把调用委托给被装饰对象,以计算价格,然后加上Mocha的价格
@Override
public String getDescription()
return beverage.getDescription() + ",Mocha";//首先利用委托的做法得到一个叙述,然后在其后加上附加的叙述
/**
* 奶泡调料:具体装饰者
*/
public class Whip extends CondimentDecorator
Beverage beverage;
public Whip(Beverage beverage)
this.beverage = beverage;
@Override
public double cost()
return beverage.cost() + 0.8;
@Override
public String getDescription()
return beverage.getDescription() + ",Whip";
/**
* 豆浆调料:具体装饰者
*/
public class Soy extends CondimentDecorator
Beverage beverage;
public Soy(Beverage beverage)
this.beverage = beverage;
@Override
public double cost()
return beverage.cost() + 0.5;
@Override
public String getDescription()
return beverage.getDescription() + ",Soy";
/**
* 测试类
*/
public class StarbuzzCoffee
public static void main(String[] args)
Beverage beverage = new Espresso(); //一杯浓缩咖啡,不要调料
System.out.println(beverage.getDescription() + " " + beverage.cost());//Espresso 1.99
Beverage beverage1 = new HouseBlend();//一杯综合咖啡
beverage1 = new Mocha(beverage1);//用摩卡装饰
beverage1 = new Mocha(beverage1);//用第二个摩卡装饰
beverage1 = new Whip(beverage1);//用奶泡装饰
beverage1 = new Soy(beverage1);//用豆浆修饰
System.out.println(beverage1.getDescription() + " " + beverage1.cost());//HouseBlend,Mocha,Mocha,Whip,Soy 2.59
以上是关于设计模式——装饰者模式的主要内容,如果未能解决你的问题,请参考以下文章