C++设计模式:享元模式 (附文字处理系统中的字符对象案例)

什么是享元模式?

享元模式是一个非常实用的结构型设计模式,它的主要目的是节省内存,尤其在需要创建大量相似对象时。

  • 通俗解释
    想象我们在写一本书,每个字母都需要表示出来。如果每个字母都单独用对象表示,不仅创建对象的成本高,而且内存占用也很大。但是,字母其实是有限的(比如英文字母只有26个大小写字母),我们可以让所有相同的字母对象共享,只存储不同的额外信息(比如字母的字体、颜色等)。

通过这种方式,我们避免了重复创建相同的字母对象,从而节省了大量内存。这就是享元模式的核心思想:将可以共享的部分抽取出来共享,用少量对象完成大量对象的表示。


享元模式的原理

享元模式的核心在于分离对象的状态

  1. 内在状态(Intrinsic State)

    • 对象中可以共享、不变的部分。
    • 例如字母的实际内容(‘A’、'B’等)就是内在状态。
  2. 外在状态(Extrinsic State)

    • 对象中不可以共享、经常变化的部分。
    • 例如字母的字体、颜色、大小等就是外在状态,由客户端传递。

通过将状态分离,享元模式可以将内在状态共享,避免重复创建对象,进而节省内存。


享元模式的组成

享元模式通常包含以下几个角色:

  1. Flyweight(享元接口):定义享元对象的公共接口。
  2. ConcreteFlyweight(具体享元类):实现享元接口,表示可以共享的具体对象。
  3. UnsharedConcreteFlyweight(非共享享元类,可选):表示不需要共享的享元类。
  4. FlyweightFactory(享元工厂):管理享元对象的创建和共享,确保相同的享元对象只会被创建一次。
  5. Client(客户端):使用享元对象,将外在状态传递给享元对象。

案例:文字处理系统中的字符对象

场景描述

我们需要实现一个简单的文字处理系统,其中:

  • 每个字符用一个对象表示。
  • 字符内容(‘A’、'B’等)是固定的,可以共享(内在状态)。
  • 字符的字体、大小、颜色等信息是外在状态,由客户端动态传递。
实现目标
  • 利用享元模式共享相同的字符对象,避免重复创建。
  • 通过外在状态动态调整字符的显示样式。

代码实现

以下是完整的代码实现,输出信息为中文,并配有详细的注释。

#include <iostream>
#include <unordered_map>
#include <string>
#include <memory>

// 抽象享元类:Flyweight
class Flyweight {
public:
    // 展示字符信息,结合外在状态进行操作
    virtual void display(const std::string& 字体, int 大小, const std::string& 颜色) const = 0;
    virtual ~Flyweight() = default;
};

// 具体享元类:ConcreteFlyweight
class ConcreteFlyweight : public Flyweight {
private:
    char 内在状态; // 内在状态,表示字符内容

public:
    explicit ConcreteFlyweight(char c) : 内在状态(c) {}

    // 实现展示方法
    void display(const std::string& 字体, int 大小, const std::string& 颜色) const override {
        std::cout << "字符: " << 内在状态 
                  << ", 字体: " << 字体 
                  << ", 大小: " << 大小 
                  << ", 颜色: " << 颜色 
                  << std::endl;
    }
};

// 享元工厂类:FlyweightFactory
class FlyweightFactory {
private:
    std::unordered_map<char, std::shared_ptr<Flyweight>> flyweights; // 存储享元对象

public:
    // 获取享元对象
    std::shared_ptr<Flyweight> getFlyweight(char c) {
        // 如果享元对象不存在,则创建一个新的对象
        if (flyweights.find(c) == flyweights.end()) {
            flyweights[c] = std::make_shared<ConcreteFlyweight>(c);
            std::cout << "创建新的享元对象: " << c << std::endl;
        }
        return flyweights[c];
    }

    // 获取享元对象的总数
    size_t getFlyweightCount() const {
        return flyweights.size();
    }
};

// 客户端代码
int main() {
    FlyweightFactory 工厂; // 创建享元工厂

    // 获取并使用享元对象
    auto 字符A = 工厂.getFlyweight('A');
    auto 字符B = 工厂.getFlyweight('B');
    auto 字符C = 工厂.getFlyweight('C');

    // 重复获取相同的享元对象
    auto 字符A2 = 工厂.getFlyweight('A');

    // 使用享元对象,传递外在状态
    字符A->display("微软雅黑", 12, "红色");
    字符B->display("宋体", 14, "蓝色");
    字符C->display("楷体", 10, "绿色");
    字符A2->display("黑体", 16, "黑色");

    // 输出享元对象的总数
    std::cout << "享元对象的总数: " << 工厂.getFlyweightCount() << " 个" << std::endl;

    return 0;
}

运行结果

运行上述代码后,输出结果如下:

创建新的享元对象: A
创建新的享元对象: B
创建新的享元对象: C
字符: A, 字体: 微软雅黑, 大小: 12, 颜色: 红色
字符: B, 字体: 宋体, 大小: 14, 颜色: 蓝色
字符: C, 字体: 楷体, 大小: 10, 颜色: 绿色
字符: A, 字体: 黑体, 大小: 16, 颜色: 黑色
享元对象的总数: 3 个

代码讲解

1. 抽象享元类
class Flyweight {
public:
    virtual void display(const std::string& 字体, int 大小, const std::string& 颜色) const = 0;
    virtual ~Flyweight() = default;
};
  • 定义了一个抽象接口display,表示字符的展示方法。
  • 外在状态(字体、大小、颜色)由客户端传递。

2. 具体享元类
class ConcreteFlyweight : public Flyweight {
private:
    char 内在状态; // 内在状态

public:
    explicit ConcreteFlyweight(char c) : 内在状态(c) {}
    void display(const std::string& 字体, int 大小, const std::string& 颜色) const override {
        std::cout << "字符: " << 内在状态 
                  << ", 字体: " << 字体 
                  << ", 大小: " << 大小 
                  << ", 颜色: " << 颜色 
                  << std::endl;
    }
};
  • 内在状态(字符内容)在对象创建时确定,表示可共享的部分。
  • display方法结合外在状态动态显示字符信息。

3. 享元工厂类
class FlyweightFactory {
private:
    std::unordered_map<char, std::shared_ptr<Flyweight>> flyweights;

public:
    std::shared_ptr<Flyweight> getFlyweight(char c) {
        if (flyweights.find(c) == flyweights.end()) {
            flyweights[c] = std::make_shared<ConcreteFlyweight>(c);
            std::cout << "创建新的享元对象: " << c << std::endl;
        }
        return flyweights[c];
    }

    size_t getFlyweightCount() const {
        return flyweights.size();
    }
};
  • 工厂管理共享对象的创建与缓存。
  • 如果享元对象不存在,则创建;如果已经存在,则直接返回。

4. 客户端代码
字符A->display("微软雅黑", 12, "红色");
字符B->display("宋体", 14, "蓝色");
字符C->display("楷体", 10, "绿色");
字符A2->display("黑体", 16, "黑色");
  • 客户端从工厂获取享元对象,并传递外在状态来控制字符的显示。
  • 字符AA2实际上是同一个对象,只是使用了不同的外在状态。

享元模式的优缺点

优点
  1. 内存节省:通过共享对象,避免了重复创建相似对象,大幅降低内存开销。
  2. 性能提升:减少了对象的创建和销毁,提升系统性能。
  3. 统一管理:通过工厂集中管理共享对象,便于维护。
缺点
  1. 复杂性增加:需要区分内在状态和外在状态,设计复杂度增加。
  2. 适用场景有限:只有当对象中有可共享部分时,享元模式才适用。

适用场景

  1. 文字处理系统:字母、数字等字符对象共享。
  2. 图形系统:共享重复出现的形状或纹理。
  3. 游戏开发:共享重复出现的敌人、道具等。

总结

享元模式通过共享内在状态,有效减少了内存开销和对象创建的成本,是一种针对性能优化的设计模式。在需要处理大量重复对象的场景中,享元模式是一个非常实用的解决方案。

本文由mdnice多平台发布

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

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

相关文章

【Leecode】Leecode刷题之路第88天之合并两个有序数组

题目出处 88-合并两个有序数组-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 88-合并两个有序数组-官方解法 方法1&#xff1a;直接合并后排序 思路&#xff1a; 代码示例&#xff1a…

Java图片拼接

最近遇到一个挺离谱的功能&#xff0c;某个表单只让上传一张图&#xff0c;多图上传会使导出失败。跟开发沟通后表示&#xff0c;这个问题处理不了。我... 遂自己思考&#xff0c;能否以曲线救国的方式拯救一下&#xff0c;即不伤及代码之根本&#xff0c;又能解决燃眉之急。灵…

bridge between Lua world and the .NET

一、新建项目&#xff1a;luademo 安装包&#xff1a;<PackageReference Include"NLua" Version"1.7.3" /> using NLua; using System;namespace luademo {internal class Program{static void Main(string[] args){Lua state new Lua();for (int …

跟着问题学23番外——反向传播算法理论(1)

前向传播与反向传播 在单层神经网络的优化算法里&#xff0c;我们讲到优化算法是为了寻找模型参数使得网络的损失值最小&#xff0c;这里详细介绍一下应用的基础——反向传播算法。 在神经网络中&#xff0c;梯度计算是通过反向传播算法来实现的。反向传播算法用于计算损失函…

Liveweb视频融合共享平台在果园农场等项目中的视频监控系统搭建方案

一、背景介绍 在我国的大江南北遍布着各种各样的果园&#xff0c;针对这些地处偏僻的果园及农场等环境&#xff0c;较为传统的安全防范方式是建立围墙&#xff0c;但是仅靠围墙仍然无法阻挡不法分子的有意入侵和破坏&#xff0c;因此为了及时发现和处理一些难以察觉的问题&…

华为IPD流程6大阶段370个流程活动详解_第二阶段:计划阶段 — 86个活动

华为IPD流程涵盖了产品从概念到上市的完整过程,各阶段活动明确且相互衔接。在概念启动阶段,产品经理和项目经理分析可行性,PAC评审后成立PDT。概念阶段则包括产品描述、市场定位、投资期望等内容的确定,同时组建PDT核心组并准备项目环境。团队培训涵盖团队建设、流程、业务…

开源轮子 - EasyExcel01(核心api)

EasyExcel01 - 核心api 本文整理自掘金大佬 - 竹子爱熊猫 https://juejin.cn/post/7405158045662576640 文章目录 EasyExcel01 - 核心api一&#xff1a;初相识EasyExcel1&#xff1a;写入excel入门2&#xff1a;读取Excel入门 二&#xff1a;数据模型注解1&#xff1a;读写通用…

实验13 C语言连接和操作MySQL数据库

一、安装MySQL 1、使用包管理器安装MySQL sudo apt update sudo apt install mysql-server2、启动MySQL服务&#xff1a; sudo systemctl start mysql3、检查MySQL服务状态&#xff1a; sudo systemctl status mysql二、安装MySQL开发库 sudo apt-get install libmysqlcli…

【java基础系列】实现数字的首位交换算法

在java中&#xff0c;手写实现一个数字的首位交换算法实现 实现效果 实现代码 核心业务代码 public static void main(String[] args) {int[] arr {1,2,3,4,5};int temp arr[0];for (int i 0; i < arr.length; i) {System.out.print(arr[i]);}System.out.println(&quo…

kubeadm一键部署K8S 集群架构

kubeadm一键部署K8S 集群架构(centos7) https://www.k8src.cn/ https://kubernetes.io/zh-cn/docs/home/ https://blog.csdn.net/m0_58709145/article/details/140128179 https://blog.csdn.net/jiaqijiaqi666/article/details/129745828 Kubeadm init报错[ERROR CRI]: contai…

【LeetCode: 876. 链表的中间结点 + 链表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Linux下基于最新稳定版ESP-IDF5.3.2开发esp32s3入门hello world输出【入门一】

开发环境搭建&#xff1a;Linux-Ubuntu下搭建ESP32的开发环境的步骤&#xff0c;使用乐鑫最新稳定版的esp-idf-CSDN博客 一、安装好开发环境后&#xff0c;在esp目录下再创建一个esp32的目录【用于编程测试demo】 二、进入esp32目录&#xff0c;打开终端【拷贝esp-idf的hello工…

单节点calico性能优化

在单节点上部署calicov3273后&#xff0c;发现资源占用 修改calico以下配置是资源消耗降低 1、因为是单节点&#xff0c;没有跨节点pod网段组网需要&#xff0c;禁用overlay方式网络(ipip&#xff0c;vxlan),使用route方式网络 配置calico-node的环境变量 CALICO_IPV4POOL_I…

基于鲲鹏服务器的打砖块小游戏部署

案例介绍 鲲鹏服务器是基于鲲鹏处理器的新一代数据中心服务器&#xff0c;适用于大数据、分布式存储、高性能计算和数据库等应用。鲲鹏服务器具有高性能、低功耗、灵活的扩展能力&#xff0c;适合大数据分析、软件定义存储、Web等应用场景。 本案例将指导开发者如何在鲲鹏服务…

#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

机器学习基础算法 (一)-线性回归

python 环境的配置参考 从零开始&#xff1a;Python 环境搭建与工具配置 线性回归的 Python 实现 线性回归是一种经典的机器学习算法&#xff0c;用于预测连续的目标变量。它假设目标变量和特征之间存在线性关系。本文将详细介绍线性回归的原理、Python 实现、模型评估和调优&…

基于linux下实现的ping程序(C语言)

linux下实现的ping程序 一、设计目的 PING程序是我们使用的比较多的用于测试网络连通性的程序。PING程序基于ICMP&#xff0c;使用ICMP的回送请求和回送应答来工作。由计算机网络课程知道&#xff0c;ICMP是基于IP的一个协议&#xff0c;ICMP包通过IP的封装之后传递。 课程设…

WPF 布局控件

wpf 布局控件有很多&#xff0c;常用的有&#xff1a;Grid, UniformGrid, Border, StackPanel, WrapPanel, DockPanel。 1. Grid Grid 经常作为控件的 Content 使用&#xff0c;常作为 Windows, UserControl 等 UI 元素的根节点。它用来展示一个 n 行 n 列的排版。 因此就有…

内网渗透横向移动技巧

在正常情况中&#xff0c;横向移动是在已经获取了足够的权限的情况下进行横向移动&#xff0c;下面中的方法大部分也需要高权限的操作。 https://www.freebuf.com/articles/network/251364.html 内网横向移动分为三种情况&#xff1a; 1.在VPN环境中进行横向移动&#xff1b; 2…

MONI后台管理系统-swagger3(springdoc-openapi)集成

springdoc-openapi Java 库有助于使用 Spring Boot 项目自动生成 API 文档。springdoc-openapi 通过在运行时检查应用程序来根据 Spring 配置、类结构和各种注释推断 API 语义。 该库会自动生成 JSON/YAML 和 HTML 格式的页面文档。生成的文档可以使用swagger-api注释进行补充。…