C++设计模式之——享元模式详解和代码案例

文章目录

    • C++中实现享元模式通常涉及以下几个关键部分:
    • 一个简单的C++代码片段示例
    • 享元模式的进一步说明
    • C++享元模式代码案例——咖啡店订单系统
    • 享元模式在现实世界的应用场景

C++中实现享元模式通常涉及以下几个关键部分:

享元模式(Flyweight Pattern)是一种用于性能优化的设计模式,它通过共享对象来有效支持大量的细粒度对象,以减少内存消耗。这种模式适用于那些对象中大部分状态可以外部化,而只有少量内部状态(不随环境改变而改变的状态)的对象。在C++中实现享元模式通常涉及以下几个关键部分:

  1. 抽象享元(Flyweight Interface)
    定义了所有具体享元类共有的接口,通常包含内部状态和外部状态的操作方法。内部状态是可以共享的,而外部状态由客户端在每次调用时传入。
class IFlyweight {
public:
    virtual ~IFlyweight() {}
    // 内部状态不变的操作
    virtual void operation(const Context& context) const = 0;
};
  1. 具体享元(Concrete Flyweight)
    实现了抽象享元接口,并存储内部状态。具体享元类的实例是可共享的。
class ConcreteFlyweight : public IFlyweight {
private:
    std::string intrinsicState; // 内部状态
public:
    ConcreteFlyweight(const std::string& state) : intrinsicState(state) {}
    void operation(const Context& context) const override {
        // 使用内部状态和外部状态进行操作
    }
};
  1. 享元工厂(Flyweight Factory)
    负责创建和管理享元对象。它确保当请求的是相同的内部状态时,不会创建多个具有相同内部状态的享元对象。
class FlyweightFactory {
private:
    std::map<std::string, std::shared_ptr<IFlyweight>> flyweights;
public:
    std::shared_ptr<IFlyweight> getFlyweight(const std::string& key) {
        if (flyweights.find(key) == flyweights.end()) {
            flyweights[key] = std::make_shared<ConcreteFlyweight>(key);
        }
        return flyweights[key];
    }
};

一个简单的C++代码片段示例

下面是一个简单的C++代码片段示例,展示了如何使用享元模式:

#include <iostream>
#include <string>
#include <map>
#include <memory>

// 抽象享元
class IFlyweight {
public:
    virtual ~IFlyweight() {}
    virtual void operation(const std::string& extrinsicState) const = 0;
};

// 具体享元
class ConcreteFlyweight : public IFlyweight {
private:
    std::string intrinsicState;
public:
    ConcreteFlyweight(const std::string& state) : intrinsicState(state) {}
    void operation(const std::string& extrinsicState) const override {
        std::cout << "Concrete Flyweight: Internal State = " << intrinsicState
                  << ", Extrinsic State = " << extrinsicState << std::endl;
    }
};

// 享元工厂
class FlyweightFactory {
private:
    std::map<std::string, std::shared_ptr<IFlyweight>> flyweights;
public:
    std::shared_ptr<IFlyweight> getFlyweight(const std::string& key) {
        if (flyweights.count(key) == 0) {
            flyweights[key] = std::make_shared<ConcreteFlyweight>(key);
        }
        return flyweights[key];
    }
};

int main() {
    FlyweightFactory factory;

    std::vector<std::string> extrinsicStates = {"state1", "state2", "state2"};

    for (const auto& state : extrinsicStates) {
        auto flyweight = factory.getFlyweight("sharedState");
        flyweight->operation(state);
    }

    return 0;
}

在这个例子中,每当客户端请求一个享元对象时,工厂会检查是否已经存在具有相同内部状态的对象。如果存在,则返回已有的对象;如果不存在,则创建新的具体享元对象并存储起来供后续请求使用。这样,多个带有不同外部状态的对象就可以共享同一个具有固定内部状态的具体享元对象,从而节约内存。

让我们深入探讨一下上面代码示例的实际意义和应用场景。在上述例子中,ConcreteFlyweight 类的内部状态是字符串 "sharedState",它是可共享的。而外部状态则是传递给 operation() 方法的参数 extrinsicState,每次调用时可能不同。

例如,假设我们正在创建一个文本渲染引擎,其中有许多字符需要绘制,但许多字符共享同一张图片资源。在这种情况下,字符的形状(字体样式、颜色等)可以视为内部状态,这部分是固定的且可以共享;而字符的位置、旋转角度、缩放比例等则可以视为外部状态,这些属性会随着字符在不同上下文中的使用而变化。

class CharacterFlyweight : public IFlyweight {
private:
    Texture texture; // 内部状态,表示字符图像纹理
public:
    CharacterFlyweight(const Texture& tex) : texture(tex) {}
    void operation(const RenderingContext& context) const override {
        // 使用共享的纹理资源,根据context中的位置、旋转角度等绘制字符
    }
};

class CharacterFactory {
private:
    std::map<CharacterStyle, std::shared_ptr<IFlyweight>> characters;
public:
    std::shared_ptr<IFlyweight> getCharacter(const CharacterStyle& style) {
        if (characters.find(style) == characters.end()) {
            Texture tex = loadTexture(style.getFontFile()); // 加载图片资源
            characters[style] = std::make_shared<CharacterFlyweight>(tex);
        }
        return characters[style];
    }
};

struct RenderingContext {
    Point position;
    double rotation;
    float scale;
    // 其他外部状态...
};

int main() {
    CharacterFactory factory;

    // 渲染不同位置的同一种字符
    CharacterStyle sharedStyle("Arial.ttf");
    RenderingContext ctx1{Point{10, 20}, 0.0, 1.0};
    RenderingContext ctx2{Point{30, 40}, 0.0, 1.0};
    auto character = factory.getCharacter(sharedStyle);
    character->operation(ctx1);
    character->operation(ctx2);

    return 0;
}

在这个例子中,CharacterFlyweight 类是具体的享元类,存储了字符的纹理(内部状态)。CharacterFactory 类作为享元工厂,负责创建和管理字符享元对象,保证相同的字符样式只加载一次图片资源。每次需要渲染字符时,客户端获取对应字符享元对象并传入上下文(即外部状态),从而有效地减少了重复加载资源造成的内存消耗。

享元模式的进一步说明

进一步说明,享元模式(Flyweight Pattern)的核心目标是通过共享技术有效支持大量细粒度的对象,从而节省系统资源。在上述文本渲染引擎的例子中:

  • CharacterFlyweight 是享元类,它封装了字符的基本视觉表现——也就是共享的纹理资源(内部状态),并且提供了 operation() 方法来执行实际的渲染操作,该方法接受 RenderingContext 对象,包含了外部状态如位置、旋转角度和缩放比例。

  • CharacterFactory 负责管理和创建享元对象,确保相同字符样式只创建一个实例。当请求某个样式的字符时,如果该样式对应的享元对象尚不存在,则加载相应的纹理资源并创建新的 CharacterFlyweight 实例;若已经存在,则直接返回已有的实例。

  • RenderingContext 表示每个字符实例的特定环境或配置,这是外部状态的具体体现,不被多个字符实例共享。每渲染一个字符时,都会根据当前的渲染上下文调整字符的表现形式。

在实际应用中,通过这样的设计,即便文档中有成千上万个同类型的字符需要渲染,也只需要保存一份共享的纹理资源,大大降低了系统的内存占用。同时,由于外部状态的变化不影响内部状态的复用,使得系统能灵活应对各种不同的渲染需求。

除此之外,享元模式还有助于减少系统中对象的数量,进而降低系统运行时的内存消耗和CPU开销。特别是在大型系统中,合理运用享元模式能够显著提升性能和响应速度。不过需要注意的是,不是所有的系统或场景都适用享元模式,应根据具体情况判断是否满足以下条件:

  1. 对象数量庞大且内部状态大部分可以共享:如果系统中存在大量相似对象,且这些对象之间的大部分状态是相同的,那么就可以考虑使用享元模式。

  2. 内部状态较少且相对稳定:内部状态是指那些不会随着环境改变而改变的状态,外部状态则是与具体使用环境相关的状态。享元对象应该尽量少地持有内部状态,并通过参数传递外部状态。

  3. 对象的创建成本高:如果对象的创建过程比较耗时或耗费资源,通过享元模式复用已有对象可以显著减少这些成本。

总结起来,享元模式在游戏开发、图形渲染、数据库连接池、缓存系统等领域有广泛应用。在实现时需权衡好内存消耗和程序逻辑复杂度的关系,确保模式的有效性和易维护性。

C++享元模式代码案例——咖啡店订单系统

以下是一个简单的C++享元模式代码案例,该案例模拟了一个咖啡店订单系统,其中咖啡口味被视为享元对象,可以被多个订单共享:

#include <iostream>
#include <map>
#include <memory>

// 抽象享元接口
class CoffeeFlavor {
public:
    virtual ~CoffeeFlavor() {}
    virtual void serve() const = 0;
};

// 具体享元类
class ConcreteCoffeeFlavor : public CoffeeFlavor {
public:
    explicit ConcreteCoffeeFlavor(const std::string& flavorName)
        : flavorName_(flavorName) {}

    void serve() const override {
        std::cout << "Serving coffee flavor: " << flavorName_ << std::endl;
    }

private:
    std::string flavorName_;
};

// 享元工厂
class CoffeeFlavorFactory {
private:
    std::map<std::string, std::shared_ptr<CoffeeFlavor>> flavors;

public:
    std::shared_ptr<CoffeeFlavor> getOrder(const std::string& flavor) {
        if (flavors.find(flavor) == flavors.end()) {
            flavors[flavor] = std::make_shared<ConcreteCoffeeFlavor>(flavor);
        }
        return flavors[flavor];
    }
};

int main() {
    CoffeeFlavorFactory factory;

    // 创建几个不同的订单,但某些口味会被共享
    std::vector<std::string> orders = {"Espresso", "Latte", "Espresso", "Cappuccino", "Espresso"};
    for (const auto& order : orders) {
        auto flavor = factory.getOrder(order);
        flavor->serve();
    }

    return 0;
}

在这个例子中:

  • CoffeeFlavor 是抽象享元类,定义了所有咖啡口味的基本行为,即服务(serve)咖啡。
  • ConcreteCoffeeFlavor 是具体享元类,实现了咖啡口味的具体行为,并存储了口味名称这一内部状态。
  • CoffeeFlavorFactory 是享元工厂,它维护了一个储存所有咖啡口味实例的映射表。当客户请求某个口味的咖啡时,工厂会检查是否已经创建过该口味的实例,如果没有则创建一个新的实例,否则返回已有的实例,从而实现了口味的共享。

运行此代码,可以看到虽然订单列表中有多个"Espresso",但在打印结果中只会看到一次"Serving coffee flavor: Espresso",这是因为享元模式让多次请求相同口味的咖啡共享了同一个实例。

实际上,上述咖啡口味享元模式的案例并没有体现出享元模式对外部状态的处理。在一个更全面的示例中,我们可能还会遇到咖啡订单具有外部状态,如客户的偏好(加糖、加奶)、杯子大小等。这时,我们可以对示例稍作修改,将外部状态从享元对象中分离出来:

#include <iostream>
#include <map>
#include <memory>

// 抽象享元接口
class CoffeeFlavor {
public:
    virtual ~CoffeeFlavor() {}
    virtual void serve(const std::string& extras, const std::string& size) const = 0;
};

// 具体享元类
class ConcreteCoffeeFlavor : public CoffeeFlavor {
public:
    explicit ConcreteCoffeeFlavor(const std::string& flavorName)
        : flavorName_(flavorName) {}

    void serve(const std::string& extras, const std::string& size) const override {
        std::cout << "Serving " << size << " cup of " << flavorName_
                  << " with extras: " << extras << std::endl;
    }

private:
    std::string flavorName_;
};

// 享元工厂
class CoffeeFlavorFactory {
private:
    std::map<std::string, std::shared_ptr<CoffeeFlavor>> flavors;

public:
    std::shared_ptr<CoffeeFlavor> getOrder(const std::string& flavor) {
        if (flavors.find(flavor) == flavors.end()) {
            flavors[flavor] = std::make_shared<ConcreteCoffeeFlavor>(flavor);
        }
        return flavors[flavor];
    }
};

// 订单类,包含外部状态
class CoffeeOrder {
public:
    CoffeeOrder(std::shared_ptr<CoffeeFlavor> flavor, const std::string& extras, const std::string& size)
        : flavor_(flavor), extras_(extras), size_(size) {}

    void serve() const {
        flavor_->serve(extras_, size_);
    }

private:
    std::shared_ptr<CoffeeFlavor> flavor_;
    std::string extras_;
    std::string size_;
};

int main() {
    CoffeeFlavorFactory factory;

    // 创建几个不同的订单,但某些口味会被共享
    std::vector<CoffeeOrder> orders = {
        {factory.getOrder("Espresso"), "no sugar", "small"},
        {factory.getOrder("Latte"), "extra foam", "medium"},
        {factory.getOrder("Espresso"), "double sugar", "large"},
        {factory.getOrder("Cappuccino"), "cinnamon", "medium"},
        {factory.getOrder("Espresso"), "single sugar", "small"}
    };

    for (const auto& order : orders) {
        order.serve();
    }

    return 0;
}

在这个修改后的示例中,我们创建了一个CoffeeOrder类,它包含了外部状态(额外配料和杯子大小),并在serve()方法中将这些外部状态传递给享元对象。即使多个订单选择了相同的咖啡口味,由于外部状态的不同,每个订单都能得到个性化的服务。

享元模式在现实世界的应用场景

讨论享元模式在现实世界的应用场景,除了前面提到的咖啡订单系统以外,还有很多其他例子可以借鉴:

  1. 字体渲染:在图形用户界面或者文字处理软件中,同一字体的不同实例可以共享字体文件数据,字体的尺寸、颜色、阴影等效果可以作为外部状态传递给字体享元对象。

  2. 图形渲染:在游戏开发中,大量的小颗粒物(如草地上的草叶、森林里的树叶等)可以共享同样的纹理和模型数据,而位置、旋转角度、缩放比例等作为外部状态传递。

  3. 数据库连接池:在Web服务器中,数据库连接是非常宝贵的资源,通过享元模式可以复用已建立的数据库连接,避免频繁创建和销毁连接带来的性能损耗。这里的连接对象就是享元对象,连接参数(数据库地址、用户名、密码等)则是外部状态。

  4. HTTP 请求缓存:在Web服务中,针对相同的URL发起的GET请求可以复用之前请求的结果,而不是每次都重新发送请求。这里HTTP请求结果可以看作享元对象,请求参数和URL作为外部状态。

在上述各个场景中,享元模式通过共享内部状态(那些不随环境变化而变化的部分)的实例,有效地节省了系统资源,提升了整体性能。同时,它通过分离内部状态和外部状态,使得系统能够灵活地应对各种复杂的业务场景。
python推荐学习汇总连接:
50个开发必备的Python经典脚本(1-10)

50个开发必备的Python经典脚本(11-20)

50个开发必备的Python经典脚本(21-30)

50个开发必备的Python经典脚本(31-40)

50个开发必备的Python经典脚本(41-50)
————————————————

​最后我们放松一下眼睛
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/419110.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java设计模式】二、单例模式

文章目录 0、单例模式1、饿汉式2、懒汉式3、双重检查4、静态内部类5、枚举6、单例模式的破坏&#xff1a;序列化和反序列化7、单例模式的破坏&#xff1a;反射8、单例模式的实际应用 设计模式即总结出来的一些最佳实现。GoF(四人组) 书中提到23种设计模式&#xff0c;可分为三大…

linux c++ 开发 tensorrt 安装

tensorrt 官方下载地址&#xff08;需要注册账号登录&#xff09;&#xff1a;Log in | NVIDIA Developer 根据系统发行版和CUDA版本 (nvcc -V) 选择合适的安装包 EA&#xff08;early access&#xff09;版本代表抢先体验。 GA&#xff08;general availability&#xff09;代…

文件对比工具Beyond Compare 4 mac v4.4.7(28397)中文版

Beyond Compare是一款适用于Windows、Mac OS X和Linux平台的文件和文件夹比较工具。它可以帮助用户比较和同步文件夹、文件和压缩包等内容&#xff0c;支持多种文件格式&#xff0c;如文本、图像、音频、视频等。 软件下载&#xff1a;Beyond Compare 4 mac v4.4.7(28397)中文版…

如何在Node.js中使用定时器

在Node.js中使用定时器是一项常见且重要的任务&#xff0c;特别是在需要执行定时任务或者轮询操作的情况下。Node.js提供了多种方式来实现定时器功能&#xff0c;包括setTimeout、setInterval和setImmediate等方法。本篇博客将介绍如何在Node.js中使用这些定时器&#xff0c;并…

WPF真入门教程30--顺风物流单据管理系统

1、教程回顾 到现在为止&#xff0c;真入门系列教程已完成了29刺由浅入深地讲解&#xff0c;当然不可能讲到了WPF的所有技能点&#xff0c;但读者看到了wpf的内部各种功能及之间的联系&#xff0c;在此基础上&#xff0c;提供一个完整有效的综合项目&#xff0c;本项目采用的是…

期货开户保证金保障市场正常运转

期货保证金是什么&#xff1f;在期货市场上&#xff0c;采取保证金交易制度&#xff0c;投资者只需按期货合约的价值&#xff0c;交一定比率少量资金即可参与期货合约买卖交易&#xff0c;这种资金就是期货保证金。期货保证金&#xff08;以下简称保证金〕按性质与作用的不同。…

AGV搬运机器人能给企业带来哪些效益?

agv 当前物流行业正在以每年40%的速度快速增长&#xff0c;却依然是典型的劳动密集型行业。随着机器人技术的崛起&#xff0c;传统物流行业也开始加大对物流科技设备的研发。AGV机器人被广泛应用于整个仓储系统内&#xff0c;疏解了一部分人力的负担&#xff0c;使后台工作更加…

使用Azure下载数据集方法

首先需要获取到下载的链接&#xff0c;例如&#xff1a; https://aimistanforddatasets01.blob.core.windows.net/cocacoronarycalciumandchestcts-2?sv2019-02-02&src&sigHvhvAtJ7KRr1uIZkjkANqozGvOsqlamMDOKcQegYLrw%3D&st2024-02-29T11%3A55%3A45Z&se2024…

水豚鼠标助手 强大的鼠标美化工具

水豚鼠标助手 水豚鼠标助手是一款 鼠标换肤、屏幕画笔、放大镜、聚光灯、屏幕放大、倒计时功能的强大屏幕演示工具。 软件助手获取 水豚鼠标助手1.0.0 安装教程 第一步&#xff1a;下载后&#xff0c;双击软件安装包 第二步&#xff1a;Windows可能会出现提示弹窗&#xff…

Mac 制作可引导安装器

Mac 使用U盘或移动固态硬盘制作可引导安装器&#xff08;以 Monterey 为例&#xff09; 本教程参考 Apple 官网相关教程 创建可引导 Mac OS 安装器 重新安装 Mac OS 相关名词解释 磁盘分区会将其划分为多个单独的部分&#xff0c;称为分区。分区也称为容器&#xff0c;不同容器…

Docker(运维工具)—— 学习笔记

快速构建、运行、管理应用的工具 一、安装docker 参考Install Docker Engine on Ubuntu | Docker Docs 二、快速入门 1、镜像和容器 docker镜像可以做到忽略操作系统的差异&#xff0c;跨平台运行&#xff0c;忽略安装的差异 当我们利用Docker安装应用时&#xff0c;Dock…

环境配置 |Jupyter lab/Jupyter Notebook 安装与设置

ipynb使用Jupyterlab/Jupyter Notebook 来编写Python程序时的文件,在使用时,可以现转换为标准的.py的python文件 1.Jupyter Lab 1.1.下载安装 环境&#xff1a;Linux pip install jupyterlab 1.2.使用 jupyter lab 点击后进入 1.3.jupyter lab更换内核 因为我的是在anac…

3d图形学基础(一):向量与坐标系

文章目录 1.1 向量与坐标系1.1.1 向量与坐标系的应用1.1.2 完整测试代码 1.1 向量与坐标系 1.1.1 向量与坐标系的应用 零向量&#xff1a; 零向量是没有方向的向量&#xff1b; 负向量&#xff1a; 负向量是与原向量方向相反、长度相等的向量&#xff1b; 向量的模&#xf…

MySQL学习Day24—数据库的设计规范

一、数据库设计的重要性: 1.糟糕的数据库设计产生的问题: (1)数据冗余、信息重复、存储空间浪费 (2)数据更新、插入、删除的异常 (3)无法正确表示信息 (4)丢失有效信息 (5)程序性能差 2.良好的数据库设计有以下优点: (1)节省数据的存储空间 (2)能够保证数据的完整性 …

matlab:涉及复杂函数图像的交点求解

matlab&#xff1a;涉及复杂函数图像的交点求解 在MATLAB中求解两个图像的交点是一个常见的需求。本文将通过一个示例&#xff0c;展示如何求解两个图像的交点&#xff0c;并提供相应的MATLAB代码。 画出图像 首先&#xff0c;我们需要绘制两个图像&#xff0c;以便直观地看…

模拟算法题练习(二)(DNA序列修正、无尽的石头)

&#xff08;一、DNA序列修正&#xff09; 问题描述 在生物学中&#xff0c;DNA序列的相似性常被用来研究物种间的亲缘关系。现在我们有两条 DNA序列&#xff0c;每条序列由 A、C、G、T 四种字符组成&#xff0c;长度相同。但是现在我们记录的 DNA序列存在错误&#xff0c;为了…

Elasticsearch入门-环境安装ES和Kibana以及ES-Head可视化插件和浏览器插件es-client

Elasticsearch入门-环境安装ES和Kibana 安装 ES Windows安装ESHead安装浏览器插件 es-clientKibana 安装 安装es,安装header 安装kibana&#xff0c;安装多种分词器ik… 安装 ES Windows安装 ① 下载压缩包并解压官网链接&#xff1a;https://www.elastic.co/cn/downloads/ela…

【MATLAB】兔子机器人腿部_simulink模型解读(及simulink中的simscape的各模块介绍)

一、动力学模型 总系统引脚含义 关节电机 Fcn 搭建方程&#xff0c;输入与输入方程 phi1 -q 大腿 小腿同理 车轮 另一边对称 虚拟腿传感器 二、控制模型 VMC解算五连杆 Pulse Generator 腿长控制器PID leg_conv.m&#xff1a;可由虚拟腿目标扭矩和推力求得电机所需…

通过jenkins进行部署java程序到centos上

1.通过jumpserver访问到centos上&#xff0c;准备下java环境 // step1: 先编辑下 vim /etc/profile// step2: 编写好环境变量 JAVA_HOME/usr/local/java export JAVA_HOME export ZOOKEEPER_HOME/opt/zookeeper/apache-zookeeper-3.7.0-bin PATH$PATH:$JAVA_HOME/bin:$ZOOKEEP…

正信晟锦:借了钱的人一直不接电话不回信息咋办

在金钱往来中&#xff0c;遇到借出的钱款无法按时回收&#xff0c;且借款人如同人间蒸发一般不接电话、不回信息&#xff0c;确实让人焦虑。面对这种情形&#xff0c;我们需采取明智而有效的措施&#xff0c;以保护自身的权益。 首要策略是保持冷静&#xff0c;不要让情绪主导行…