装饰模式

背景

给一个类或一个对象增加行为?继承关联。装饰模式可以不需要创造更多子类的情况下,将对象的功能拓展。动态地给一个对象增加一些额外的职责,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式相同,但它们适用于不同的场合。

代码示例

beverage.h,饮料的接口,其中有名字和价格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
@ beverage.h
**/
#ifndef _BEVERAGE_H
#define _BEVERAGE_H
#include "stdio.h"
//被装饰类的结接口(抽象)
class beverage{
public:
virtual void name() = 0;
virtual int price() = 0;
};
#endif

被装饰的类coffee,具体的饮料coffee,价格50元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
@ coffee.h
**/
#ifndef _COFFEE_H_
#define _COFFEE_H_
#include "beverage.h"
class coffee :public beverage{
public:
void name(){
printf("coffee\n");
};
int price(){
return 50;
}
};
#endif

装饰类,给coffee装饰了milk,coffee_milk.h,相当于增加了被装饰类(coffee)的功能(加了牛奶)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
@ coffee_milk.h
**/
#ifndef _COFFEE_MILK_H_
#define _COFFEE_MILK_H_
#include "coffee.h"
class coffee_milk : public beverage{
private:
beverage *_beverage;
public:
coffee_milk(beverage *b):_beverage(b){};
void name(){
_beverage->name();
printf("with milk \n");
};
int price(){
return _beverage->price() + 20;
};
};
#endif

此外,该装饰类是给饮料加上牛奶,不仅仅是给咖啡,只要满足berveage接口的饮料,都能“装饰”上牛奶。如果有4中饮料,5种装饰,系统的类只需要11个,就可以组合成任意类型的饮品。

优点

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。

  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。

  3. 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。

  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”

缺点

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。

  2. 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐

适用环境

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  2. 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。

  3. 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类).

模式比较

代理模式和装饰器模式

相同点

装饰者和被装饰者都实现了同一接口,代理类和真实类也实现了同一接口。

在真实的对象方法前后加上自定义的方法。

不同点

装饰模式关注动态创建方法,而代理模式关注控制对象的访问。

在代理模式中,是由代理模式创建真实类的对象,客户不能拥有真实类的对象;而装饰器模式先生成真实类的对象,并将其作为参数传递给装饰器的构造函数。

适配器模式和装饰模式

相同点

别名相同。

不同点

装饰模式要求接口不变,方法增强;而适配器模式则要求功能不变,接口改变。