装饰者模式是一个结构型设计模式,用于在不修改原始对象的基础上动态地给对象添加新的功能。装饰者模式通过创建一个包装对象(装饰者),对原始对象进行封装,并对原始对象的功能进行扩展或修改。这使得在运行时可以灵活地为对象添加或删除功能,而不需要修改原始对象的代码。
装饰者模式的关键点如下:
1:继承和组合:装饰者和原始对象共享相同的接口(或继承自相同的抽象类)。这使得装饰者可以透明地替代原始对象。装饰者可以通过组合的方式引用原始对象,从而可以在执行原始对象的功能的同时,添加或修改其行为。
2:动态扩展:装饰者模式允许在运行时为对象添加或删除功能,而不需要修改原始对象的代码,这增加了程序的灵活性和可扩展性。
3:层叠装饰:装饰者可以嵌套使用,从而可以逐层地为对象添加功能,每个装饰者都可以对原始对象的功能进行扩展或修改,并将结果传递给下一个装饰者。
四个角色
装饰者模式主要包括以下四个角色:
1:Component(组件接口):所有被装饰组件及装饰器对应的接口标准,指定进行装饰的行为方法。对应本章例程中的展示接口Showable。
2:ConcreteComponent(组件实现):需要被装饰的组件,实现组件接口标准,只具备自身未被装饰的原始特性。对应本章例程中的女生类Girl。
3: Decorator(装饰器):装饰器的高层抽象类,同样实现组件接口标准,且包含一个被装饰的组件。
4:ConcreteDecorator(装饰器实现):继承自装饰器抽象类的具体子类装饰器,可以有多种实现,在被装饰组件对象的基础上为其添加新的特性。对应本章例程中的粉底类FoundationMakeup、口红类Lipstick。
优点跟缺点
优点:
1:更好的扩展性:装饰者模式允许在不修改原始对象代码的情况下动态地扩展对象的功能。这符合开放-封闭原则,即对扩展开放,对修改关闭。
2:更大的灵活性:装饰者模式可以在运行时为对象添加或删除功能,提高了更大的灵活性。此外,装饰者和原始对象共享相同的接口,使得装饰者可以透明地替代原始对象。
3:更易于组合:装饰者模式通过组合而非继承来实现对象间的关系,这使得我们可以创建多个不同的装饰者并按需堆叠在一起。这有助于减少类的数量,提高代码的可维护性。
4:符合单一职责原则:具体装饰者负责实现单一的功能扩展,这使得每个类的职责更加清晰,符合单一职责原则。
缺点:
1:代码复杂度增加:虽然装饰者可以减少子类的数量,但它引入了抽象装饰者类和具体装饰者类,这可能导致代码结构变得复杂,不易理解。
2:增加对象数量:由于每个具体装饰者都需要包装一个具体组件,这会导致对象数量的增加,大量的对象可能会影响程序的性能。
3:难以跟踪装饰过程:当装饰者模式被多层堆叠嵌套使用时,可能会导致调用链过长,不利于调试和维护。
生活中的例子
我们可以将生活中的房子装修跟女生化妆类比为装饰者模式。在这些例子中,我们可以看到类似装饰者模式的核心思想—在不修改原始对象的基础上,增加额外的功能或特性。
房子装修:原始房子可以看到是基本对象,而装修过程就是添加各种装饰(例如:涂漆,贴壁纸,购买家具等)。在这个过程中,我们并没有改变房子本身的结构,而是通过装修来改善房子的外观和功能。
女生化妆:化妆可以看做是对女生容貌的装饰,在这个过程中,我们并没有改变女生的基本特征,而是通过化妆来改善外观,增强自信。化妆品(例如:口红,眼影,腮红等)可以看做是不同的装饰者,它们可以在不改变基本特征的情况下,为女生的容貌添加各种效果。
这些例子与装饰者模式的核心思想相似,即在不修改原始对象的基础上,动态地添加新的功能或特性。虽然这些例子并不是软件设计中的应用场景,但它们有助于我们更好地理解装饰者模式的概念。
工作应用场景
以下是一些在实际工作中可能使用装饰者模式的例子:
1:日志记录
假设你有一个类(DatabaseService)用来执行数据库操作,如插入,更新,删除。现在你想在执行这些操作时记录日志,但不想修改原始类的代码,你可以创建一个装饰者(LoggingDatabaseService),它包装DatabaseService并在每个操作之前或之后记录日志。
2:缓存
假设你有一个类(ProductService)用于从数据库检索产品信息,为了提高性能,你想在内存中缓存产品数据,你可以创建一个装饰者(CacheingProductService),它包装ProductService并在检索产品数据时添加缓存逻辑,这样,你就可以在不动原始类的情况下为其添加缓存功能。
3:数据压缩/加密
假设你有一个类(FileStorage
)用于将数据存储到文件中。现在,你想在将数据存储到文件之前对数据进行压缩或加密。你可以创建一个装饰者(CompressedFileStorage
或 EncryptedFileStorage
),它包装 FileStorage
并在存储数据之前对数据进行压缩或加密。这样,你可以在不修改原始类的情况下为其添加压缩或加密功能。
这些例子展示了如何使用装饰者模式在不修改原始类的情况下为对象添加新功能。装饰者模式通过继承和组合实现了对象之间的灵活依赖关系,使得可以在运行时为对象添加或删除功能。
代码实现
假设我们有一个简单的咖啡店,它提供各种类型的咖啡,如浓缩咖啡(Espresso)、拿铁咖啡(Latte)等。此外,顾客还可以为咖啡添加各种调料,如牛奶、摩卡、焦糖等。
1:抽象组件 – Beverage
abstract class Beverage
{
protected $description = 'unknown Beverage';
abstract public function getDescription();
abstract public function cost();
}
2:具体组件
#Espresso
class Espresso extends Beverage
{
public function __construct()
{
$this->description = ‘Espresso’;
}
public function getDescription(): string
{
return $this->description;
}
public function cost()
{
return 1.99;
}
}
#Latte
class Latte extends Beverage
{
public function __construct()
{
$this->description = “Latte”;
}
public function getDescription(): string
{
return $this->description;
}
public function cost(): float
{
return 2.49;
}
}
3:抽象装饰者
abstract class CondimentDecorator extends Beverage
{
abstract public function getDescription();
}
4:具体装饰者
#Milk
class Milk extends CondimentDecorator
{
private $beverage;
public function __construct(Beverage $beverage)
{
$this->beverage = $beverage;
}
public function getDescription(): string
{
return $this->beverage->getDescription() . ",Milk";
}
public function cost(): float
{
return $this->beverage->cost() + 0.29;
}
}
#Mocha
class Mocha extends CondimentDecorator
{
private $beverage;
public function __construct(Beverage $beverage)
{
$this->beverage = $beverage;
}
public function getDescription(): string
{
return $this->beverage->getDescription() . ",Mocha";
}
public function cost(): float
{
return $this->beverage->cost() + 0.35;
}
}
#Caramel
class Caramel extends CondimentDecorator
{
private $beverage;
public function __construct(Beverage $beverage)
{
$this->beverage = $beverage;
}
public function getDescription(): string
{
return $this->beverage->description . ",Caramel";
}
public function cost(): float
{
return $this->beverage->cost() + 0.25;
}
}
5:开始使用
// 创建一个 Espresso 咖啡
$beverage1 = new Espresso();
echo $beverage1->getDescription() . "$" . $beverage1->cost() . "\n";
// 创建一个 Latte 咖啡,加 Mocha 和 Milk
$beverage2 = new Latte();
$beverage2 = new Mocha($beverage2);
$beverage2 = new Milk($beverage2);
echo $beverage2->getDescription() . "$" . $beverage2->cost() . "\n";
// 创建一个 Espresso 咖啡,加 Caramel、Mocha 和 Milk
$beverage3 = new Espresso();
$beverage3 = new Caramel($beverage3);
$beverage3 = new Mocha($beverage3);
$beverage3 = new Milk($beverage3);
echo $beverage3->getDescription() . "$" . $beverage3->cost() . "\n";
在这个例子中,我们使用了组合而不是继承来实现咖啡和调料之间的关系。这使得我们可以在运行时为咖啡动态地添加或删除调料,而无需创建大量子类来表示所有可能的功能组合。这个例子展示了装饰者模式如何提高代码的灵活性和可维护性。
参考书籍:
《秒懂设计模式》
《Head First 设计模式》