简单介绍
装饰模式是一种结构型设计模式,当你需要在一个类里扩展一个或者额外添加一个新行为,可以用装饰模式在原有基础上加一层 " 装饰 "
开发时遇到的问题
例如你有一个开发一个提供通知功能的库, 其他程序可使用它向用户发送关于重要事件的通知
但是用户会在使用中希望添加许多功能。
(有的用户希望在微信上接收消息, 有的用户则希望在 QQ 上接收消息,有的希望两个都接收)
这时候如果盲目使用继承扩展通知类就会导致代码量膨胀。
继承也会引发一些问题:
- 继承是静态的。 你无法在运行时更改已有对象的继承关系, 只能使用由不同子类创建的对象来替代当前的整个对象。
- 子类只能有一个父类。 大部分编程语言不允许一个类同时继承多个类的行为。
有一种方法是使用聚合或组合 , 两者的工作方式几乎一模一样: 一个对象包含指向另一个对象的引用, 并将部分工作委派给引用对象; 继承中的对象则继承了父类的行为, 它们自己能够完成这些工作。
这时候就可以引入装饰模式的思想:装饰器连接其他目标对象(上述的扩展的功能类),包含目标对象的方法(在微信上接收消息),会将所有接收到的请求委派给目标对象,并且可以在委派前后对请求进行处理。可能会改变最终结果。
客户端无法分辨是通知类Notifier 还是装饰器BaseDecorator 使用了通用的接口,减少耦合
UML图
实现方式
确保业务逻辑可用一个基本组件及多个额外可选层次表示。(咖啡为基本组件,选择添加的东西为可选层次)
以下代码主要注意继承关系以及 聚合关系
#include <iostream>
#include <bits/stdc++.h>
// 装饰模式
using namespace std;
// 当子类有相同的字段,需要向上提,(可以设置一个中间类)
class coffee
{
public:
coffee() {}
virtual string desc() = 0;
virtual int money() = 0;
~coffee() {}
};
class BlockCoffee : public coffee
{
public:
virtual string desc()
{
return "黑咖啡";
}
virtual int money()
{
return 10;
}
};
class Decorator : public coffee
{
public:
Decorator(coffee *coffee) : decor(coffee)
{
}
coffee *decor;//聚合关系
};
class suger : public Decorator
{
public:
suger(coffee *coffee) : Decorator(coffee) {} // 调用基类的构造器
virtual string desc()
{
return decor->desc() + "一勺糖";
}
virtual int money()
{
return decor->money() + 1;
}
};
class Chocolate : public Decorator
{
public:
Chocolate(coffee *coffee) : Decorator(coffee) {}
virtual string desc()
{
return decor->desc() + "一块巧克力";
}
virtual int money()
{
return decor->money() + 3; // 一块巧克力3¥
}
};
int main()
{
BlockCoffee *bc = new BlockCoffee();
printf("一杯黑咖啡 %s %d",bc->desc(),bc->money());
cout << bc->desc() << " " << bc->money() << endl;
suger *su = new suger(bc);
// 计算一杯黑咖啡加一勺糖的价格
cout << su->desc() << " " << su->money() << endl;
// 加一勺牛奶
Chocolate *ct = new Chocolate(su);
// 计算一杯黑咖啡加一勺糖、加一勺牛奶、加一块巧克力的价格
cout << ct->desc() << " " << ct->money() << endl;
system("pause");
}
适合应用场景
如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。
当需要在运行时为对象新增额外的行为时,可以通过创建不同的具体装饰者对象来实现。这些具体装饰者对象可以根据需要进行组合,从而实现各种不同的行为组合,而无需修改原始对象的代码。保持了接口不变
如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。
许多编程语言使用 final最终关键字来限制对某个类的进一步扩展。 复用最终类已有行为的唯一方法是使用装饰模式: 用封装器对其进行封装。
与其他模式的关系
-
适配器模式 可以对已有对象的接口进行修改, 装饰模式 则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合(在装饰上装饰), 适配器则无法实现。
-
适配器模式 能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰模式 则能为对象提供加强的接口。
-
责任链模式和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。
责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。
- 组合模式 和装饰的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。
装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。
但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。