原理图:
装饰器(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
在 OO 设计和开发过程, 可能会经常遇到以下的情况: 我们需要为一个已经定义好的类
添加新的职责(操作), 通常的情况我们会给定义一个新类继承自定义好的类,这样会带来
一个问题( 将在本模式的讨论中给出)。通过继承的方式解决这样的情况还带来了系统的复
杂性,因为继承的深度会变得很深。
而 Decorator 提供了一种给类增加职责的方法,不是通过继承实现的,而是通过组合。
在 结 构 图 中 , ConcreteComponent 和 Decorator 需 要 有 同 样 的 接 口 , 因 此
ConcreteComponent 和 Decorator 有着一个共同的父类。这里有人会问,让 Decorator 直接维
护一个指向 ConcreteComponent 引用(指针) 不就可以达到同样的效果, 答案是肯定并且是
否定的。 肯定的是你可以通过这种方式实现, 否定的是你不要用这种方式实现, 因为通过这
种方式你就只能为这个特定的 ConcreteComponent 提供修饰操作了,当有了一个新的ConcreteComponent 你 又 要 去 新 建 一 个 Decorator 来 实 现 。 但 是 通 过 结 构 图 中 的
ConcreteComponent 和 Decorator 有一个公共基类, 就可以利用 OO 中多态的思想来实现只要
是 Component 型别的对象都可以提供修饰操作的类,这种情况下你就算新建了 100 个
Component 型别的类 ConcreteComponent,也都可以由 Decorator 一个类搞定。这也正是
Decorator 模式的关键和威力所在了。
--摘自 二十三种设计模式
Decorator 模式和 Proxy 模式的相似的地方在于它们都拥有一个指向其他对象的引用(指
针), 即通过组合的方式来为对象提供更多操作(或者 Decorator 模式) 间接性( Proxy 模式)。
但是他们的区别是, Proxy 模式会提供使用其作为代理的对象一样接口, 使用代理类将其操
作都委托给 Proxy 直接进行。这里可以简单理解为组合和委托之间的微妙的区别了。
Decorator 模式除了采用组合的方式取得了比采用继承方式更好的效果, Decorator 模式
还给设计带来一种“即用即付” 的方式来添加职责。 在 OO 设计和分析经常有这样一种情况:
为了多态, 通过父类指针指向其具体子类, 但是这就带来另外一个问题, 当具体子类要添加
新的职责, 就必须向其父类添加一个这个职责的抽象接口, 否则是通过父类指针是调用不到
这个方法了。这样处于高层的父类就承载了太多的特征( 方法),并且继承自这个父类的所
有子类都不可避免继承了父类的这些接口, 但是可能这并不是这个具体子类所需要的。 而在
Decorator 模式提供了一种较好的解决方法, 当需要添加一个操作的时候就可以通过
Decorator 模式来解决,你可以一步步添加新的职责。
--摘自 二十三种设计模式
代码示例如下:
package design.decorator;
//抽象构件角色
interface Component {
public void operation();
}
//装饰模式 :指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
public class DecoratorPattern {
public static void main(String[] args) {
Component p = new ConcreteComponent();
System.out.println("---------------------------------");
ConcreteDecorator1 d = new ConcreteDecorator1(p);
ConcreteDecorator2 d2 = new ConcreteDecorator2(d);
d2.operation();
}
}
//抽象装饰角色
abstract class Decorator implements Component {
public abstract void operation();
}
//具体构件角色
class ConcreteComponent implements Component {
public ConcreteComponent() {
System.out.println("创建具体构件角色");
}
public void operation() {
System.out.println("调用具体构件角色的方法operation()");
}
}
//具体装饰角色
class ConcreteDecorator1 extends Decorator {
Component component;
public ConcreteDecorator1(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
addedFunction();
}
public void addedFunction() {
System.out.println("ConcreteDecorator1为具体构件角色增加额外的功能addedFunction()");
}
}
//具体装饰角色
class ConcreteDecorator2 extends Decorator {
Component component;
public ConcreteDecorator2(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
addedFunction();
}
public void addedFunction() {
System.out.println("ConcreteDecorator2为具体构件角色增加额外的功能addedFunction()");
}
}
运行结果:
创建具体构件角色
---------------------------------
调用具体构件角色的方法operation()
ConcreteDecorator1为具体构件角色增加额外的功能addedFunction()
ConcreteDecorator2为具体构件角色增加额外的功能addedFunction()
扩展示例:咖啡设计
现在呢,有一个咖啡馆,它有一套自己的订单系统,当顾客来咖啡馆的时候,可以通过订单系统来点自己想要的咖啡。他们原先的设计是这样子的:
此时、咖啡馆为了吸引更多的顾客,需要在订单系统中允许顾客选择加入不同调料的咖啡,例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。
下面是他们的第一次尝试:
这种设计肯定是不行的,简直分分钟把人逼疯的节奏,有木有!
3、这时,有个人提出了新的方案,利用实例变量和继承,来追踪这些调料。
具体为:先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡……),
这种设计虽然满足了现在的需求,但是我们想一下,如果出现下面情况,我们怎么办,
①、调料价钱的改变会使我们更改现有代码。
②、一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。
③、以后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)。
④、万一顾客想要双倍摩卡咖啡,怎么办?
很明显,上面的设计并不能够从根本上解决我们所碰到的问题。并且这种设计违反了 开放关闭原则(类应该对扩展开放,对修改关闭。)。
那我们怎么办呢?好啦,装饰者可以非常完美的解决以上的所有问题,让我们有一个设计非常nice的咖啡馆。
扩展示例:咖啡设计实现
下面我们就利用装饰者模式来实现一个全新的咖啡馆。
1、我们要以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:
①、拿一个深焙咖啡(DarkRoast)对象
②、以摩卡(Mocha)对象装饰它
③、以奶泡(Whip)对象装饰它
④、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
好了!但是如何“装饰”一个对象,而“委托”又要如何与此搭配使用呢?那就是把装饰者对象当成“包装者”。让我们看看这是如何工作的:
我们将我们所知道的写下来:
①、装饰者和被装饰对象有相同的超类型。
②、你可以用一个或多个装饰者包装一个对象。
③、既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
④、装饰者可以在所委托被装饰者的行为之前与 / 或之后,加上自己的行为,以达到特定的目的。
⑤、对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
下面,我们来看一下装饰者模式的类图:
package design.decorator;
import java.math.BigDecimal;
/**
* 我们要以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:
* <p>
* ①、拿一个深焙咖啡(DarkRoast)对象
* <p>
* ②、以摩卡(Mocha)对象装饰它
* <p>
* ③、以奶泡(Whip)对象装饰它
* <p>
* ④、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
*/
/**
* 咖啡馆(供应咖啡)
*/
public class BeverageDecorator {
public static void main(String[] args) {
//订一杯Espresso(2.00),不需要调料,打印出它的描述与价钱。
Beverage beverage = new Espresso();
System.out.println("Description: " + beverage.getDescription() + " $" + beverage.cost());
//制造出一个DarkRoast(3.00)对象,用Mocha(0.2)装饰它,用第二个Mocha(0.2)装饰它,用Whip(0.4)装饰它,打印出它的描述与价钱。
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println("Description: " + beverage2.getDescription() + " $" + beverage2.cost());
//再来一杯调料为豆浆(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的Decaf(低咖啡因咖啡 4.00),打印出它的描述与价钱。
Beverage beverage3 = new Decaf();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println("Description: " + beverage3.getDescription() + " $" + beverage3.cost());
}
}
// 饮料抽象类
abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
/**
* cost方法是用来返回饮料的价钱(需在具体类中自己实现)
*
* @return
*/
public abstract BigDecimal cost();
}
/**
* 深焙咖啡类(一种具体的饮料)
*/
class DarkRoast extends Beverage {
/**
* 说明他是DarkRoast饮料
*/
public DarkRoast() {
description = "DarkRoast";
}
/**
* 实现cost方法,用来返回DarkRoast(深焙咖啡)的价格
*
* @return
*/
@Override
public BigDecimal cost() {
return new BigDecimal("3.00");
}
}
/**
* 低咖啡因咖啡类(一种具体的饮料)
*/
class Decaf extends Beverage {
/**
* 说明他是Decaf饮料
*/
public Decaf() {
description = "Decaf";
}
/**
* 实现cost方法,用来返回Decaf(低咖啡因咖啡)的价格
*
* @return
*/
@Override
public BigDecimal cost() {
return new BigDecimal("4.00");
}
}
/**
* 浓缩咖啡类(一种具体饮料)
*/
class Espresso extends Beverage {
/**
* 说明他是Espresso饮料
*/
public Espresso() {
description = "Espresso";
}
/**
* 实现cost方法,用来返回Espresso(浓缩咖啡)的价格
*
* @return
*/
@Override
public BigDecimal cost() {
return new BigDecimal("2.00");
}
}
//调料装饰着抽象类(继承自饮料抽象类)
abstract class CondimentDecorator extends Beverage {
/**
* 所有的调料装饰者都必须重新实现getDescription()方法
* 这样才能够用递归的方式来得到所选饮料的整体描述
*
* @return
*/
public abstract String getDescription();
}
/**
* 摩卡调料类(继承自CondimentDecorator)
*/
class Mocha extends CondimentDecorator {
/**
* 用一个实例变量记录饮料,也就是被装饰者
*/
Beverage beverage;
/**
* 构造器初始化饮料变量
*
* @param beverage
*/
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
/**
* 在原来饮料的基础上添加上Mocha描述(原来的饮料加入Mocha调料,被Mocha调料装饰)
*
* @return
*/
@Override
public String getDescription() {
return beverage.getDescription() + ",Mocha";
}
/**
* 在原来饮料的基础上加上Mocha的价格(原来的饮料加入Mocha调料,被Mocha调料装饰)
*
* @return
*/
@Override
public BigDecimal cost() {
return new BigDecimal("0.2").add(beverage.cost());
}
}
/**
* 豆浆调料类(继承自CondimentDecorator))
*/
class Soy extends CondimentDecorator {
/**
* 用一个实例变量记录饮料,也就是被装饰者
*/
Beverage beverage;
/**
* 构造器初始化饮料变量
*
* @param beverage
*/
public Soy(Beverage beverage) {
this.beverage = beverage;
}
/**
* 在原来饮料的基础上添加上Soy描述(原来的饮料加入Soy调料,被Soy调料装饰)
*
* @return
*/
@Override
public String getDescription() {
return beverage.getDescription() + ",Soy";
}
/**
* 在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
*
* @return
*/
@Override
public BigDecimal cost() {
return new BigDecimal("0.3").add(beverage.cost());
}
}
/**
* 奶泡调料类(继承自CondimentDecorator)
*/
class Whip extends CondimentDecorator {
/**
* 用一个实例变量记录饮料,也就是被装饰者
*/
Beverage beverage;
/**
* 构造器初始化饮料变量
*
* @param beverage
*/
public Whip(Beverage beverage) {
this.beverage = beverage;
}
/**
* 在原来饮料的基础上添加上Whip描述(原来的饮料加入Whip调料,被Whip调料装饰)
*
* @return
*/
@Override
public String getDescription() {
return beverage.getDescription() + ",Whip";
}
/**
* 在原来饮料的基础上加上Whip的价格(原来的饮料加入Whip调料,被Whip调料装饰)
*
* @return
*/
@Override
public BigDecimal cost() {
return new BigDecimal("0.4").add(beverage.cost());
}
}
运行结果:
Description: Espresso $2.00
Description: DarkRoast,Mocha,Mocha,Whip $3.80
Description: Decaf,Soy,Mocha,Whip $4.90
优缺点:
1、优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
2、缺点:多层装饰比较复杂。
JAVA IO使用的装饰模式:
文章参考:
咖啡的示例完全来源于: