设计模式之装饰模式(学习笔记)

· 浏览次数 : 33

小编点评

装饰模式是一种结构型设计模式,它提供了一种在不改变现有对象结构的情况下,动态地添加新功能的方式。这种模式通过将每个功能封装在单独的装饰器类中,并且这些装饰器类通过引用原始对象来实现功能的组合,从而提供了灵活性和可扩展性的优势。 使用装饰模式的优点包括: 1. **灵活性**:装饰模式允许在不改变现有对象结构的情况下,动态地添加新的功能。 2. **可扩展性**:通过组合不同的装饰器类,可以实现多种功能组合,符合开闭原则。 3. **单一职责原则**:每个装饰器类只关注于一个特定的功能或责任,使得代码结构更加清晰和易于维护。 然而,装饰模式也存在一些缺点: 1. **复杂性增加**:可能会导致装饰器类的数量增加,增加了系统的复杂度和理解难度。 2. **装饰顺序问题**:如果装饰器的顺序不正确,可能会影响最终的功能实现。 装饰模式适用于以下适用场景: 1. **动态添加功能**:当需要动态地为对象添加额外功能时,而又不希望生成大量子类时。 2. **透明且灵活地扩展对象的功能**:当需要在不影响其他对象的情况下动态添加功能时,装饰模式尤为适用。 在咖啡店的例子中,我们使用装饰模式实现了添加不同调料的功能。通过定义抽象组件类`Coffee`,具体组件类`BasicCoffee`,以及抽象装饰器类`CoffeeDecorator`和具体装饰器类`MilkDecorator`、`SugarDecorator`,我们可以动态地给咖啡添加不同的调料,而不需要修改`Coffee`类的代码。

正文

定义

装饰模式(Decorator Pattern),又称为包装模式,是一种结构型设计模式。它允许在不改变现有对象结构的情况下,动态地添加新的功能。通过将每个功能封装在单独的装饰器类中,并且这些装饰器类通过引用原始对象来实现功能的组合,从而提供了灵活性和可扩展性的优势。装饰模式避免了通过继承方式增加功能所带来的复杂性,使得功能的添加和组合更加高效和清晰。

为什么使用装饰模式?

  1. 灵活性和可扩展性

    • 装饰模式允许按需动态地添加或移除对象的功能,而不会影响其他部分的代码。
    • 通过组合不同的装饰器类,可以创建出多种不同的对象组合,以满足不同的需求,避免了静态继承所带来的类爆炸问题。
  2. 单一职责原则

    • 每个装饰器类只关注于一个特定的功能或责任,这符合单一职责原则,使得代码结构更加清晰和易于维护。
  3. 透明性

    • 装饰器类与原始对象实现相同的接口,因此对客户端来说是透明的。
    • 客户端可以像使用原始对象一样使用装饰后的对象,无需关心对象内部的具体装饰结构。

装饰模式的实现步骤

  1. 抽象组件类

    • 定义了被装饰对象的接口,它可能是一个抽象类或接口,包含了所有具体组件类和装饰器类都会实现的方法(抽象被装饰者的行为)。
  2. 具体组件类

    • 实现抽象组件接口,表示原始对象的基本行为或功能(继承抽象组件类,被装饰者行为的具体实现)。
  3. 抽象装饰器类

    • 扩展了抽象组件类,同时持有一个指向抽象组件对象的引用。
    • 这个类可以选择性地添加一些额外的行为,但其主要作用是通过引用调用原始对象的方法(继承抽象组件类)。
  4. 具体装饰器类

    • 扩展了抽象装饰器类,通过在调用父类方法前后添加新的行为来实现功能的扩展。
    • 具体装饰器类可以是多个,可以互相组合,以形成复杂的装饰结构(继承抽象装饰器类)。

优缺点和适用场景

优点

  1. 灵活性

    • 动态地为对象添加功能,避免了静态继承的限制。
  2. 可扩展性

    • 通过组合不同的装饰器类,可以实现多种功能组合,符合开闭原则。
  3. 单一职责原则

    • 每个装饰器类只关注于一个功能,使得代码结构清晰。

缺点

  1. 复杂性增加

    • 可能会导致装饰器类的数量增加,增加了系统的复杂度和理解难度。
  2. 装饰顺序问题

    • 如果装饰器的顺序不正确,可能会影响最终的功能实现。

适用场景

  1. 动态添加功能

    • 当需要动态地为对象添加额外功能时,而又不希望生成大量子类时,可以使用装饰模式。
  2. 透明且灵活地扩展对象的功能

    • 当需要透明且灵活地扩展对象的功能时,装饰模式尤为适用,例如在不影响其他对象的情况下动态添加功能。

咖啡店的例子

假设我们有一个咖啡店,卖基础的咖啡和各种附加调料(如牛奶、糖、巧克力等)。我们可以使用装饰模式来实现这种功能扩展。
#include <iostream>
#include <memory>  // 提供智能指针的定义和实现
// 抽象组件类:咖啡
class Coffee {
public:
    virtual ~Coffee() {}
    // 获取咖啡描述的方法,纯虚函数
    virtual std::string getDescription() const = 0;
    // 获取咖啡价格的方法,纯虚函数
    virtual double cost() const = 0;
};
// 具体组件类:基本咖啡
class BasicCoffee : public Coffee {
public:
    // 实现获取描述的方法
    std::string getDescription() const override {
        return "Basic Coffee";
    }
    // 实现获取价格的方法
    double cost() const override {
        return 5.0; // 基本咖啡的价格
    }
};
// 抽象装饰器类:咖啡装饰器
class CoffeeDecorator : public Coffee {
protected:
    // 持有一个指向被装饰对象的指针
    std::shared_ptr<Coffee> coffee;
public:
    // 构造函数,接受一个被装饰对象的指针
    CoffeeDecorator(std::shared_ptr<Coffee> coffee) : coffee(coffee) {}
    // 实现获取描述的方法,调用被装饰对象的方法
    std::string getDescription() const override {
        return coffee->getDescription();
    }
    // 实现获取价格的方法,调用被装饰对象的方法
    double cost() const override {
        return coffee->cost();
    }
};
// 具体装饰器类:牛奶装饰器
class MilkDecorator : public CoffeeDecorator {
public:
    // 构造函数,接受一个被装饰对象的指针
    MilkDecorator(std::shared_ptr<Coffee> coffee) : CoffeeDecorator(coffee) {}
    // 实现获取描述的方法,添加牛奶的描述
    std::string getDescription() const override {
        return coffee->getDescription() + ", Milk";
    }
    // 实现获取价格的方法,添加牛奶的价格
    double cost() const override {
        return coffee->cost() + 1.5; // 牛奶的价格
    }
};
// 具体装饰器类:糖装饰器
class SugarDecorator : public CoffeeDecorator {
public:
    // 构造函数,接受一个被装饰对象的指针
    SugarDecorator(std::shared_ptr<Coffee> coffee) : CoffeeDecorator(coffee) {}
    // 实现获取描述的方法,添加糖的描述
    std::string getDescription() const override {
        return coffee->getDescription() + ", Sugar";
    }
    // 实现获取价格的方法,添加糖的价格
    double cost() const override {
        return coffee->cost() + 0.5; // 糖的价格
    }
};
int main() {
    // 创建一个基本咖啡对象
    std::shared_ptr<Coffee> basicCoffee = std::make_shared<BasicCoffee>();
    std::cout << "Description: " << basicCoffee->getDescription() << ", Cost: " <<  basicCoffee->cost() << " RMB" << std::endl;
    // 用牛奶装饰基本咖啡
    std::shared_ptr<Coffee> coffeeWithMilk =  std::make_shared<MilkDecorator>(basicCoffee);
    std::cout << "Description: " << coffeeWithMilk->getDescription() << ", Cost: "  << coffeeWithMilk->cost() << " RMB" << std::endl;
    // 再用糖装饰已加牛奶的咖啡
    std::shared_ptr<Coffee> coffeeWithMilkAndSugar =  std::make_shared<SugarDecorator>(coffeeWithMilk);
    std::cout << "Description: " << coffeeWithMilkAndSugar->getDescription() << ",  Cost: " << coffeeWithMilkAndSugar->cost() << " RMB" << std::endl;
    return 0;
}

 

与设计模式之装饰模式(学习笔记)相似的内容:

设计模式之装饰模式(学习笔记)

定义 装饰模式(Decorator Pattern),又称为包装模式,是一种结构型设计模式。它允许在不改变现有对象结构的情况下,动态地添加新的功能。通过将每个功能封装在单独的装饰器类中,并且这些装饰器类通过引用原始对象来实现功能的组合,从而提供了灵活性和可扩展性的优势。装饰模式避免了通过继承方式增加

设计模式学习(九):装饰器模式

设计模式学习(九):装饰器模式 作者:Grey 原文地址: 博客园:设计模式学习(九):装饰器模式 CSDN:设计模式学习(九):装饰器模式 装饰器模式 装饰器模式是一种结构型模式。 顾名思义,就是对某个方法或者对象进行装饰,举个简单的例子,有个圆形类 Circle,我需要把这个圆形的涂上红色,其实

软件设计模式系列之十一——装饰模式

装饰模式属于结构型设计模式,它通过将对象包装在装饰器类中来动态地添加额外的行为,而不需要修改原始对象的代码。这个模式以透明的方式向对象添加功能,从而使您可以根据需要组合各种功能。

【23种设计模式】装饰模式(九)

前言 装饰模式,英文名称:Decorator Pattern。我第一次看到这个名称想到的是另外一个词语“装修”,我就说说我对“装修”的理解吧,大家一定要看清楚,是“装修”,不是“装饰”。在房子装修的过程中,各种功能可以相互组合,来增加房子的功用。类似的,如果我们在软件系统中,要给某个类型或者对象增加

【23种设计模式】适配器模式(六)

## 前言 从今天开始我们开始讲【结构型】设计模式,【结构型】设计模式有如下几种:**适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式**。【创建型】的设计模式解决的是对象创建的问题,那【结构型】设计模式解决的是类和对象的组合关系的问题。 今天我们就开始讲【结构型】设计模式里面

当装饰者模式遇上Read Through缓存,一场技术的浪漫邂逅

在《经验之谈:我为什么选择了这样一个激进的缓存大Key治理方案》一文中,我提到在系统中使用的缓存是旁路缓存模式,有读者朋友问,有没有用到过其他的缓存模式,本文将结合一个我曾经工作中的案例,使用装饰者模式实现Read Through缓存模式,助你轻松掌握设计模式和缓存。

【23种设计模式】组合模式(八)

前言 组合模式,英文名称是:Composite Pattern。当我们谈到这个模式的时候,有一个物件和这个模式很像,也符合这个模式要表达的意思,那就是“俄罗斯套娃”。“俄罗斯套娃”就是大的瓷器娃娃里面装着一个小的瓷器娃娃,小的瓷器娃娃里面再装着更小的瓷器娃娃,直到最后一个不能再装更小的瓷器娃娃的那个

以开发之名|线上家装新美学——梦想之家,由你来定

何谓家装?过去,人们辗转于各大家装城,购买时下流行的家具装饰,参考各类“过来人”和设计师的意见,糅杂一些样板间风格,依靠想象拼凑一个设计方案。而今天,在年轻一代的消费群体心中,家装的意义正发生深刻改变:要自由定义,坚决悦己,实用与美观兼得;要猫与鱼,花与叶,喜欢的音乐,收藏的手办都有自己的天地;要一

设计模式之适配器模式(学习笔记)

定义 适配器模式是一种结构型设计模式,它允许将一个类的接口转换为客户端希望的另一个接口。适配器使得原本由于接口不兼容而不能一起工作的类可以协同工作。通过创建适配器类,可以将现有类的接口转换成目标接口,从而使这些类能够在一起工作。 为什么使用适配器模式 兼容性 适配器模式能够解决由于接口不兼容而无法直

设计模式之抽象工厂模式(学习笔记)

定义 抽象工厂模式是一种创建型设计模式,它提供一个接口,用于创建一系列相关或依赖的对象,而无需指定它们的具体类。抽象工厂模式将对象的创建过程抽象化,允许子类通过实现具体工厂类来定制对象的创建。 为什么使用抽象工厂模式 产品族的一致性 抽象工厂模式确保同一产品族中的对象之间的一致性。 部分遵循开闭原则