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

news2024/11/15 11:55:01

文章目录

    • 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.coloradmin.cn/o/1490497.html

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

相关文章

【Linux网络】再谈 “协议“

目录 再谈 "协议" 结构化数据的传输 序列化和反序列化 网络版计算器 封装套接字操作 服务端代码 服务进程执行例程 启动网络版服务端 协议定制 客户端代码 代码测试 使用JSON进行序列化与反序列化 我们程序员写的一个个解决我们实际问题&#xff0c;满…

一手实测【Claude3】 - GPT4啊,你的时代终于要过去了

通过虚拟卡 WildCard 的方式来升级Claude3最快了&#xff0c;大概2分钟就可以开通完成, 而且升级 GPT 4.0 价钱也不贵&#xff0c;虚拟卡一年10美元&#xff0c;Claude3 每个月也才 20美元。如果你觉得Claude3对你可能有帮助&#xff0c;那就赶快来升级吧&#xff01; Claude3…

这本书太好了!150页就能让你上手大模型应用开发

如果问个问题&#xff1a;有哪些产品曾经创造了伟大的奇迹&#xff1f;ChatGPT 应该会当之无愧入选。仅仅发布 5 天&#xff0c;ChatGPT 就吸引了 100 万用户——当然&#xff0c;数据不是关键&#xff0c;关键是其背后的技术开启了新的 AI 狂潮&#xff0c;成为技术变革的点火…

多功能声学综合馆:革新解决气膜场馆噪音难题

近年来&#xff0c;气膜场馆在各类活动中的广泛应用带来了许多便利&#xff0c;但其内部噪音问题也一直困扰着人们。为了有效解决这一挑战&#xff0c;多功能声学综合馆崭露头角&#xff0c;通过创新的声学技术成为解决气膜场馆噪音问题的独特方案。 在这个嘈杂的世界中&#x…

第三篇【传奇开心果系列】Python的自动化办公库技术点案例示例:深度解读Pandas股票市场数据分析

传奇开心果博文系列 系列博文目录Python的自动化办公库技术点案例示例系列 博文目录前言一、Pandas进行股票市场数据分析常见步骤和示例代码1. 加载数据2. 数据清洗和准备3. 分析股票价格和交易量4. 财务数据分析 二、扩展思路介绍1. 技术指标分析2. 波动性分析3. 相关性分析4.…

MYSQL07高级_Hash结构、平衡二叉树、B树、B+树介绍

文章目录 ①. 全表遍历②. Hash结构③. 平衡二叉搜索树(AVL)④. B树⑤. B树⑥. 时间复杂度 选择的合理性 磁盘的I/O操作次数对索引的使用效率至关重要查找都是索引操作,一般来说索引非常大,尤其是关系型数据库,当数据量比较大的时候,索引的大小有可能几个G甚至更多,为了减少索引…

头像剪切上传

头像剪切上传 文章说明核心Api示例源码效果展示源码下载 文章说明 本文主要为了学习头像裁剪功能&#xff0c;以及熟悉canvas绘图和转文件的相关操作&#xff0c;参考教程&#xff08;Web渡一前端–图片裁剪上传原理&#xff09; 核心Api 主要就一个在canvas绘图的操作 context…

【触想智能】工业一体机刷卡应用知识分享

工业一体机刷卡技术是一种高效、稳定、安全的身份认证方式&#xff0c;具有广泛的应用场景和优势。在工业自动化控制、生产过程监测等领域&#xff0c;它已成为必不可少的设备之一。 一、工业一体机刷卡的原理:工业一体机刷卡的原理和普通的刷卡设备类似&#xff0c;都是通过读…

VS2022如何添加行号?(VS2022不显示行号解决方法)

VS2022不显示行号解决方法 VS2022是非常好用的工具&#xff0c;很多同学在初学C/C的时候&#xff0c;都会安装&#xff0c;默认安装好VS2022后&#xff0c;写代码时&#xff0c;在编辑框的窗口左边就有显示行号&#xff0c;如下图所示&#xff1a; 但是有些同学安装好后&#…

阿里云中小企业扶持权益

为企业提供云资源和技术服务&#xff0c;助力企业开启智能时代创业新范式。阿里云推出中小企业扶持权益 上云必备&#xff0c;助力企业长期低成本用云 一、ECS-经济型e实例、ECS u1实例活动规则 活动时间 2023年10月31日0点0分0秒至2026年3月31日23点59分59秒 活动对象 同时满…

GEE:使用Sigmoid激活函数对单波段图像进行变换(以NDVI为例)

作者:CSDN @ _养乐多_ 本文将介绍在 Google Earth Engine (GEE)平台上,对任意单波段影像进行 Sigmoid 变换的代码。并以对 NDVI 影像像素值的变换为例。 文章目录 一、Sigmoid激活函数1.1 什么是 Sigmoid 激活函数1.2 用到遥感图像上有什么用?二、代码链接三、完整代码一…

Tomcat概念、安装及相关文件介绍

目录 一、web技术 1、C/S架构与B/S架构 1.1 http协议与C/S架构 1.2 http协议与B/S架构 2、前端三大核心技术 2.1 HTML&#xff08;Hypertext Markup Language&#xff09; 2.2 css&#xff08;Cascading Style Sheets&#xff09; 2.3 JavaScript 3、同步和异步 4、…

吴恩达机器学习全课程笔记第七篇

目录 前言 P114-P120 推荐系统 协同过滤 均值归一化 协同过滤的tensorflow实现 查找相关项目 P121-P130 基于内容的过滤 强化学习 P131-P142 状态动作值函数定义 Bellman方程 随机环境 连续状态空间应用实例 前言 这是吴恩达机器学习笔记的第七篇&#xff0c;…

linux kernel物理内存概述(二)

目录 物理内存数据结构 设备数物理内存描述 物理内存映射 map_kernel map_mem zone数据结构 zone类型 物理内存数据结构 站在处理器角度&#xff0c;管理物理内存的最小单位是页面。使用page数据结构描述&#xff0c;通常默认大小4kB&#xff0c;采用mem_map[]数组来存…

(每日持续更新)jdk api之PipedWriter基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

合作的终极策略,竟如此有数学规律?《多Agent系统引论》第6章 多Agent交互 原文注释

6.0 前言 本文介绍一下多Agent交互过程中的一些概念&#xff0c;并且我保证能给你在人类社会中的工作生活学习带来启发。 6.1 效用和偏好 6.1.1 不知道什么是效用&#xff1f;那我告诉你什么是边际效应递减&#xff01; 想象一个人&#xff0c;他总资产只有1块钱&#xff0c;…

Vue前端+快速入门【详解】

目录 1.Vue概述 2. 快速入门 3. Vue指令 4.表格信息案例 5. 生命周期 1.Vue概述 1.MVVM思想 原始HTMLCSSJavaScript开发存在的问题&#xff1a;操作麻烦&#xff0c;耦合性强 为了实现html标签与数据的解耦&#xff0c;前端开发中提供了MVVM思想&#xff1a;即Model-Vi…

什么是 End-to-End 测试?

在使用 vue 的模板创建新项目的时候&#xff0c;有一个选项是问&#xff0c;是否添加“端到端”测试&#xff1f;说实在我不知道&#xff0c;而且三个选项一个都不认识。 ? Add an End-to-End Testing Solution? › - Use arrow-keys. Return to submit. ❯ NoCypressNigh…

QChart柱状图

//柱状图// 创建柱状图数据QBarSet *set0 new QBarSet("");*set0 << 1601 << 974 << 655 << 362;QBarSeries *series new QBarSeries();series->append(set0);set0->setColor(QColor("#F5834B"));// 创建柱状图QChart *ch…

基于springboot+vue的美食烹饪互动平台

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…