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)
————————————————

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LCR 153. 二叉树中和为目标值的路径

解题思路&#xff1a; 回溯&#xff1a;先序遍历&#xff0b;路径记录 class Solution {LinkedList<List<Integer>> res new LinkedList<>();LinkedList<Integer> path new LinkedList<>();public List<List<Integer>> pathTarge…

android 如何动态修改swap

前言 当前项目中发现&#xff0c;产品在长时间使用后&#xff0c;会概率死机&#xff0c;通过log分析&#xff0c;可能和swap 大小太小导致的&#xff0c;需要修改增大swap大小后&#xff0c;压测验证。如何查看swap大小 cat /proc/swaps C:\Users\Administrator>adb shel…

元学习(meta-learning)的通俗解释

目录 1、什么是元学习 2、元学习还可以做什么 3、元学习是如何训练的 1、什么是元学习 meta-learning 的一个很经典的英文解释是 learn to learn&#xff0c;即学会学习。元学习是一个很宽泛的概念&#xff0c;可以有很多实现的方式&#xff0c;下面以目标检测的例子来解释…

阿里Replace Anything:一键替换万物,让图像编辑更简单

最近&#xff0c;阿里巴巴智能研究院在AIGC领域可谓动作频频&#xff0c;新品发布不断&#xff0c;在之前的文章已经向大家介绍了关于Animate AnyOne, Outfit Anyone&#xff0c;AnyText, AnyDoor等相关技术&#xff0c;感兴趣的小伙伴可以点击下面链接阅读&#xff5e; AI一键…

Laravel - API 项目适用的图片验证码

1. 安装 gregwar/captcha 图片验证码接口的流程是&#xff1a; 生成图片验证码 生成随机的 key&#xff0c;将验证码文本存入缓存。 返回随机的 key&#xff0c;以及验证码图片 # 不限于 laravel 普通 php 项目也可以使用额 $ composer require gregwar/captcha2. 开发接口 …

小塔RFID技术帮您解决“仓储管理危机”!

商品积压对一个企业带来的影响是久远的&#xff0c;仓储管理流转失衡&#xff1a;库存数据不准确、繁琐人工管理费时费力、商品爆仓及库存短缺等造成“仓储管理危机”&#xff0c;让企业自身陷入困境。 优化仓储管理&#xff0c;小塔RFID仓储管理方案轻松解决。利用RFID&#x…

java数据结构与算法刷题-----LeetCode538. 把二叉搜索树转换为累加树

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 解题思路 BST二叉搜索树&#xff0c;中序遍历结果为一个升序序列…

【C语言】三子棋

前言&#xff1a; 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏规则是双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#xff0c;率先将自己的三个棋子走成一条线就视为胜利。但因棋盘太小&#xff0c;三子棋在很多时候会出现和…

Unity(第十四部)光照

原始的有默认灯光、除了默认的你还可以创建 1、定向光源&#xff08;类似太阳、从无限远的地方射向地面的光&#xff0c;光源位置并不影响照射角度等&#xff0c;不同方向的旋转影响角度和明亮&#xff09; 1. 颜色&#xff1a;调整光的颜色2. 模式&#xff1a;混合是实时加烘…

FCU2601嵌入式控制单元获得开普「电磁兼容检验证书」

近日&#xff0c;飞凌嵌入式专为锂电池储能行业设计的FCU2601嵌入式控制单元获得了开普电磁兼容检验证书&#xff0c;此次性能检验项目包括高频干扰检验、静电放电干扰检验、辐射电磁场干扰检验、快速瞬变脉冲群干扰检验、浪涌干扰检验、工频磁场干扰检验、阻尼振荡磁场干扰检验…

基于docker实现MySQL主从复制(全网最详细!!!)

一、 通过docker镜像搭建MySQL主从 主服务器&#xff1a;容器名zi-mysql-master&#xff0c;端口3306 从服务器&#xff1a;容器名zi-mysql-slave1&#xff0c;端口3307 从服务器&#xff1a;容器名zi-mysql-slave2&#xff0c;端口3308 二、 关闭防火墙&#xff0c;启动docker…

免费百度快速收录软件

在网站SEO的过程中&#xff0c;不断更新网站内容是提升排名和吸引流量的关键之一。而对于大多数网站管理员来说&#xff0c;频繁手动更新文章并进行SEO优化可能会是一项繁琐且耗时的任务。针对这一问题&#xff0c;百度自动更新文章SEO工具应运而生&#xff0c;它能够帮助网站管…

CCF-A类 IEEE VIS‘24 3月31日截稿!探索可视化技术的无限可能!

会议之眼 快讯 IEEE VIS (IEEE Visualization Conference )即可视化大会将于 2024 年 10月13日 -18日在美国佛罗里达州皮特海滩的信风岛大海滩度假举行&#xff01;圣彼得海滩&#xff0c;以其迷人的日落和和煦的微风&#xff0c;作为激发创造力和促进可视化社区内合作的完美背…

工厂模式 详解 设计模式

工厂模式 其主要目的是封装对象的创建过程&#xff0c;使客户端代码和具体的对象实现解耦。这样子就不用每次都new对象&#xff0c;更换对象的话&#xff0c;所有new对象的地方也要修改&#xff0c;违背了开闭原则&#xff08;对扩展开放&#xff0c;对修改关闭&#xff09;。…

win中删除不掉的文件,火绒粉碎删除亲测有效

看网上的 win R 然后终端输入什么删除的&#xff0c;照做了都没有删掉 有火绒的可以试试&#xff1a; 拖进去就删掉了 很好使

选项 打光 试题总结

试题1 被测物体100100mm&#xff0c;精度要求被测物体 &#xff0c;精度要求0.1mm&#xff0c;相机距被测物体在200&#xff5e;320mm之间&#xff0c;要求选择合适的相机和镜头&#xff1f; 分析如下&#xff1a; 通常我们用的相机靶面是4:3 的所以我们要用短边来计算视场&am…

Jmeter系列(5)线程数到底能设置多大

疑惑 一台设备的线程数到底可以设置多大&#xff1f; 线程数设置 经过一番搜索找到了这样的答案&#xff1a; Linux下&#xff0c;2g的 java内存&#xff0c;1m 的栈空间&#xff0c;最大启动线程数2000线程数建议不超过1000jmeter 能启动多少线程&#xff0c;由你的堆内存…

Tomcat 下部署若依单体应用可观测最佳实践

实现目标 采集指标信息采集链路信息采集日志信息采集 RUM 信息会话重放 即用户访问前端的一系列过程的会话录制信息&#xff0c;包括点击某个按钮、操作界面、停留时间等&#xff0c;有助于客户真是意图、操作复现 版本信息 Tomcat (9.0.81)Springboot(2.6.2)JDK (>8)DDT…

ensp路由器将不同网络连通在一起

1.拓扑结构信息如下 二层交换机&#xff1a;lsw2,lsw3,lsw5,lsw6 不进行ip配置&#xff0c;只是定义vlan&#xff0c;和主机标注的保持一致&#xff0c;向下连接pc用access&#xff0c;向上连接路由交换机用trunk lsw2配置信息如下图 定义vlan&#xff0c;设置各个连接口的方式…

tcpdump 常用用法

简要记录下tcpdump用法 监控某个ip上的某个端口的流量 tcpdump -i enp0s25 tcp port 5432 -nn -S 各个参数作用 -i enp0s25 指定抓包的网卡是enp0s25 -nn 显示ip地址和数字端口 &#xff0c;如果只 -n 则显示ip&#xff0c;但是端口为services文件中的服务名 如果一个…