目录
-
- 星巴兹咖啡的故事
- 装饰者模式
-
- 一、模式介绍
- 二、模式实现
- 三、模式设计
- 四、模式的优缺点
- 五、使用场景:
- 六、装饰者用到的设计原则:
星巴兹咖啡的故事
- 现在有一个咖啡馆,他有一套自己的订单系统,当顾客来咖啡馆的时候,可以通过订单系统来点自己想要的咖啡。他们原先的设计是这样子的:
软件设计模式 | 结构型之装饰者模式详解 - 每一种饮料都继承抽象饮料类 Beverage,计算价钱只需要调用相应子类的 cost() 方法即可,其中 description 是其相应的饮料描述。饮料少的情况下完全没问题,但咖啡馆为了吸引更多的顾客,在系统中允许顾客选择加入不同的调料,如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。如果说依旧用原来的设计那么整个系统会出现爆炸的状况。(当然我只画了一小部分,再多点 visio 就崩溃了 /(ㄒoㄒ)/ )
软件设计模式 | 结构型之装饰者模式详解 - 这时,有个人提出了新的方案,利用实例变量和继承,来追踪这些调料。具体为:先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡……)
软件设计模式 | 结构型之装饰者模式详解 软件设计模式 | 结构型之装饰者模式详解 这种设计虽然满足了现在的需求,但也存在非常大的局限性:
最主要的问题就是,如果顾客选择了双份的奶泡(配料),该怎么办?
其次,出现了新的材料,那么还需要修改父类中的 cost() 方法,这违反了开放关闭原则(类应该对扩展开放,对修改关闭)。
…
那我们怎么办呢?好啦,装饰者可以非常完美的解决以上的所有问题,让我们有一个设计非常nice的咖啡馆。
装饰者模式
一、模式介绍
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
我们通过下面的实例来演示装饰器模式的用法。
二、模式实现
以摩卡和奶泡深焙咖啡为例:
步骤:
- 拿到一个深焙咖啡(DarkRoast)对象
- 以摩卡(Mocha)对象去装饰它
- 以奶泡(Whip)对象去装饰它
- 调用 cost() 方法
图示步骤:
- 拿到一个深焙咖啡(DarkRoast)对象
软件设计模式 | 结构型之装饰者模式详解 - 顾客要摩卡(Mocha),所以建立 Mocha 对象,并用它将 DarkRoast 对象装起来
软件设计模式 | 结构型之装饰者模式详解 - 顾客要奶泡(Whip),所以建立 Whip 对象,并用它将以 Mocha 装饰的 DarkRoast 对象装起来 可以把 Mocha 装饰之后的 DarkRoast 仍然看成是一个 Beverage ,原因在于多态!无论是装饰者摩卡(Mocha)、奶泡(Whip)还是被装饰者深焙咖啡 归根结底都是继承着顶层的抽象类(Beverage)。因此无论 DarkRoast 被多少装饰者装饰着包装着都是一个Beverage。
软件设计模式 | 结构型之装饰者模式详解 - 调用 cost() 方法,递归实现计算最终价钱。
三、模式设计
装饰者模式的模板类图:
装饰者模式所包含的角色如下:
- Component : 抽象构件角色,定义对象的接口,可以给这些对象动态增加职责。
- ConcreteComponent : 被装饰者,它是我们将要动态地加上新行为的对象,它扩展自Component。
- Decorator : 这是装饰者共同实现的接口(抽象类)。
- ConcreteDecator : 具体的装饰者,可以加上新的方法。新行为使用过在旧行为前面或者后面做一些计算来添加的,并且每一个装饰者 都有一个实例变量,它可以记录所装饰的事物。
根据以上模板类图来绘制出订单系统的类图:
代码如下:
Beverage : 抽象饮料类
/**
* 抽象饮料类
*/
public abstract class Beverage {
String name = "Unknown Beverage";
//返回饮料名字
public String getName() {
return name;
}
//cost方法是用来返回饮料的价钱,需在具体类中自己实现
public abstract int cost();
}
具体饮料类:被装饰者,包括:独家调配的咖啡(HouseBlend)、深焙咖啡(DarkRoast)、浓缩咖啡(Espresso)、低咖啡因咖啡类(Decaf)
/**
* 独家调配的咖啡类(一种具体饮料)
*/
public class HouseBlend extends Beverage {
//说明他是HouseBlend饮料
public HouseBlend() {
name = "独家调配的咖啡";
}
//实现cost方法,用来返回 独家调配的咖啡 的价格
@Override
public int cost() {
return 9;
}
}
/**
* 深焙咖啡类(一种具体饮料)
*/
public class DarkRoast extends Beverage {
//说明他是DarkRoast饮料
public DarkRoast() {
name = "深焙咖啡";
}
//实现cost方法,用来返回 深焙咖啡 的价格
@Override
public int cost() {
return 11;
}
}
/**
* 浓缩咖啡类(一种具体饮料)
*/
public class Espresso extends Beverage {
//说明他是Espresso饮料
public Espresso() {
name = "浓缩咖啡";
}
//实现cost方法,用来返回 浓缩咖啡 的价格
@Override
public int cost() {
return 12;
}
}
/**
* 低咖啡因咖啡类(一种具体饮料)
*/
public class Decaf extends Beverage {
//说明他是Decaf饮料
public Decaf() {
name = "低咖啡因咖啡";
}
//实现cost方法,用来返回 低咖啡因咖啡 的价格
@Override
public int cost() {
return 10;
}
}
Dector : 装饰者共同实现的接口
/**
* 调料装饰着抽象类(继承自饮料抽象类)
*/
public abstract class Decorator extends Beverage {
/**
* 所有的调料装饰者都必须重新实现getDescription()方法
* 这样才能够用递归的方式来得到所选饮料的整体描述
*/
public abstract String getName();
}
ConcreteDecorator : 具体的装饰者,包括:牛奶(Milk)、摩卡(Mocha)、豆浆(Soy)、奶泡(Whip)
/**
* 牛奶调料类(继承自 Decorator)
*/
public class Milk extends Decorator {
//用一个实例变量记录饮料,也就是被装饰者
Beverage beverage;
//构造器初始化饮料变量
public Milk(Beverage beverage) {
this.beverage = beverage;
}
//在原来饮料的基础上添加上Milk描述(原来的饮料加入Milk调料,被Milk调料装饰)
@Override
public String getName() {
return beverage.getName() + ",牛奶";
}
//在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
@Override
public int cost() {
return beverage.cost() + 3;
}
}
/**
* 摩卡调料类(继承自 Decorator)
*/
public class Mocha extends Decorator {
//用一个实例变量记录饮料,也就是被装饰者
Beverage beverage;
//构造器初始化饮料变量
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
//在原来饮料的基础上添加上Mocha描述(原来的饮料加入Mocha调料,被Mocha调料装饰)
@Override
public String getName() {
return beverage.getName() + ",摩卡";
}
//在原来饮料的基础上加上Mocha的价格(原来的饮料加入Mocha调料,被Mocha调料装饰)
@Override
public int cost() {
return beverage.cost() + 2;
}
}
/**
* 豆浆调料类(继承自 Decorator)
*/
public class Soy extends Decorator {
//用一个实例变量记录饮料,也就是被装饰者
Beverage beverage;
//构造器初始化饮料变量
public Soy(Beverage beverage) {
this.beverage = beverage;
}
//在原来饮料的基础上添加上Soy描述(原来的饮料加入Soy调料,被Soy调料装饰)
@Override
public String getName() {
return beverage.getName() + ",豆浆";
}
//在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
@Override
public int cost() {
return beverage.cost() + 1;
}
}
/**
* 奶泡调料类(继承自 Decorator)
*/
public class Whip extends Decorator {
//用一个实例变量记录饮料,也就是被装饰者
Beverage beverage;
//构造器初始化饮料变量
public Whip(Beverage beverage) {
this.beverage = beverage;
}
//在原来饮料的基础上添加上 Milk 描述(原来的饮料加入 Whip 调料,被 Whip 调料装饰)
@Override
public String getName() {
return beverage.getName() + ",奶泡";
}
//在原来饮料的基础上加上 Whip 的价格(原来的饮料加入Soy调料,被 Whip 调料装饰)
@Override
public int cost() {
return beverage.cost() + 4;
}
}
Client : 客户端演示代码
public class Client {
public static void main(String[] args) {
System.out.println("饮料价格如下:");
System.out.println("独家调配的咖啡类:9、低咖啡因咖啡:10、深焙咖啡类:11、浓缩咖啡类:12");
System.out.println("调料价格如下:");
System.out.println("豆浆:1、摩卡:2、牛奶:3、奶泡:4\n");
//订一杯浓缩咖啡类Espresso(12) 对象,不需要调料,打印出它的描述与价钱。
Beverage beverage1 = new Espresso();
System.out.println("饮料描述: " + beverage1.getName() + " 价格为: " + beverage1.cost());
System.out.println("---------------------------------------------------");
//制造出一个深焙咖啡类DarkRoast(11) 对象,用摩卡Mocha(2)装饰它,用第二个摩卡Mocha(2)装饰它,用奶泡Whip(4)装饰它,打印出它的描述与价钱。
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
System.out.println("饮料描述: " + beverage2.getName() + " 价格为: " + beverage2.cost());
System.out.println("---------------------------------------------------");
//也可用这种形式
Beverage beverage3 = new Mocha( new Mocha(new DarkRoast()));
System.out.println("饮料描述: " + beverage3.getName() + " 价格为: " + beverage3.cost());
System.out.println("---------------------------------------------------");
//再来一杯 低咖啡因咖啡Decaf(10)对象,并用豆浆Soy(1)、摩卡Mocha(2)、奶泡Whip(4),打印出它的描述与价钱。
Beverage beverage4 = new Decaf();
beverage4 = new Soy(beverage4);
beverage4 = new Mocha(beverage4);
beverage4 = new Whip(beverage4);
System.out.println("饮料描述: " + beverage4.getName() + " 价格为: " + beverage4.cost());
}
}
结果展示:
四、模式的优缺点
优点: 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点: 多层装饰比较复杂。
五、使用场景:
- 扩展一个类的功能。
-
动态增加功能,动态撤销。
六、装饰者用到的设计原则:
- 多用组合,少用继承。
- 对扩展开放,对修改关闭。
本文的主要思路以及开篇引述的故事都借鉴于下面这篇博客,博主写的非常清晰,如果没有看懂的地方,可前往链接查看详情。
参考博客:https://www.cnblogs.com/of-fanruice/p/11565679.html