21.享元模式:思考与解读

原文地址:享元模式:思考与解读  更多内容请关注:深入思考与解读设计模式

引言

在软件开发中,特别是当你处理大量相似对象时,是否会遇到一个问题:大量的对象会占用大量的内存,而这些对象有许多相同的状态?你是否觉得,重复创建相似的对象会浪费内存,并且增加系统的负担?是否有一种方式,可以通过共享相同的对象来节约内存,并提高系统的效率?

享元模式正是为了解决这一问题而设计的。它通过共享相同的对象来减少内存的使用,特别是在对象的状态中有很多相同部分时。你是否理解,为什么通过共享对象的不可变部分,可以显著降低内存开销?

在本文中,我们将通过一系列问题,逐步引导你理解享元模式的核心思想、应用场景以及如何实现它。

什么是享元模式?

问题1:当你需要创建大量对象时,是否曾遇到过内存占用过大的问题?你通常如何处理大量相似对象的创建?

假设你有一个系统,需要创建大量的对象,每个对象有相同的部分状态。你是如何管理这些对象的?是否每个对象都创建一个新实例,还是有其他的方式来减少内存开销?

问题2:如果有一种方式,能够在创建大量相似对象时,只保留不同的部分,而共享相同的部分,这样是否能显著减少内存的使用?

享元模式通过共享相同的对象部分,只保留对象的可变部分来节约内存。你是否理解,为什么这种方式能够提高内存的利用率,尤其是在对象中存在大量相同状态时?

享元模式的核心概念

问题3:享元模式通常包含哪些角色?每个角色的职责是什么?

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

  1. 享元(Flyweight):定义了共享对象的接口,并可以通过外部传入的状态来进行区分。

  2. 具体享元(ConcreteFlyweight):实现享元接口,提供共享对象的具体实现。

  3. 享元工厂(FlyweightFactory):负责管理享元对象的创建和共享,确保相同的享元对象只创建一次。

  4. 客户端(Client):使用享元对象,并将外部状态传递给共享对象。

你能理解这些角色是如何协同工作的?它们如何通过共享相同的对象来减少内存的占用?

问题4:为什么要引入享元工厂来管理享元对象的创建和共享?这种方式如何保证享元对象的复用?

享元工厂负责创建和管理享元对象,确保相同的对象只创建一次。你是否理解,为什么享元工厂能够有效地减少对象的重复创建,从而提高系统的效率和内存使用?

问题5:享元模式是如何区分对象的内部状态和外部状态的?为什么享元对象的内部状态是共享的,而外部状态是独立的?

享元模式通过将不可变的部分(内部状态)共享,而将可变的部分(外部状态)保留在客户端。你是否理解,为什么内部状态可以共享,而外部状态需要传递给享元对象?

享元模式的实现

假设我们正在开发一个图形编辑系统,其中每个图形都有相同的颜色和形状,但可能有不同的位置。我们将使用享元模式来共享相同颜色和形状的图形实例,从而节约内存。

步骤1:定义享元接口

from abc import ABC, abstractmethodclass Shape(ABC):@abstractmethoddef draw(self, x: int, y: int):pass

问题6:为什么我们需要定义一个享元接口(Shape)?它的作用是什么?

Shape接口定义了所有具体享元类需要实现的draw()方法,使得客户端能够使用统一的接口来操作不同类型的图形。你能理解,为什么通过接口来定义共享对象的行为,可以让享元模式更加灵活?

步骤2:定义具体享元类
class Circle(Shape):def __init__(self, color: str):self.color = color  # 共享的内部状态def draw(self, x: int, y: int):print(f"Drawing a {self.color} circle at ({x}, {y})")

问题7:Circle类是如何实现Shape接口的?它如何管理内部状态?

Circle类实现了Shape接口,并通过color来管理共享的内部状态。你是否理解,为什么图形的颜色可以作为共享的状态,而位置是独立的?

步骤3:定义享元工厂类
class ShapeFactory:def __init__(self):self._shapes = {}def get_shape(self, color: str) -> Shape:if color not in self._shapes:self._shapes[color] = Circle(color)return self._shapes[color]

问题8:ShapeFactory类是如何管理享元对象的?它如何确保相同颜色的图形只创建一次?

ShapeFactory类通过维护一个享元对象池(_shapes字典),确保相同颜色的图形只创建一次。你是否理解,为什么这种方式能够有效避免重复创建相同的享元对象?

步骤4:客户端代码
def main():factory = ShapeFactory()# 获取不同位置的相同颜色的圆形shape1 = factory.get_shape("red")shape2 = factory.get_shape("blue")shape3 = factory.get_shape("red")shape1.draw(10, 20)  # Drawing a red circle at (10, 20)shape2.draw(30, 40)  # Drawing a blue circle at (30, 40)shape3.draw(50, 60)  # Drawing a red circle at (50, 60)print(f"shape1 and shape3 are the same object: {shape1 is shape3}")  # Trueif __name__ == "__main__":main()

问题9:在客户端代码中,如何通过享元工厂来获取享元对象?为什么相同颜色的图形对象只会创建一次?

客户端通过ShapeFactory来获取享元对象,并且相同颜色的图形对象会复用。你是否理解,为什么这种方式让图形对象的内存占用变得更加高效,且避免了重复创建相同对象?

享元模式的优缺点

问题10:享元模式的优点是什么?它如何帮助我们节省内存,并提高系统的性能?

享元模式通过共享相同的状态来减少内存的占用,尤其是在处理大量相似对象时。你是否理解,这种方式如何显著提高内存的利用率,并且提高系统的性能?

问题11:享元模式的缺点是什么?它是否可能导致客户端的复杂性增加?

尽管享元模式能够节约内存,但它也可能导致客户端需要管理外部状态,增加客户端的复杂性。你是否认为,在某些情况下,享元模式可能会让系统的设计变得更加复杂?如何在使用享元模式时平衡内存节约与代码复杂性?

适用场景

问题12:享元模式适用于哪些场景?

享元模式特别适用于以下场景:

  • 当你需要处理大量相似对象,而这些对象有相同的不可变状态时。

  • 当对象状态中有大量重复的部分,可以共享时。

  • 当需要在大量对象中减少内存占用时。

你能想到其他类似的场景吗?例如,游戏中的纹理对象、图形界面组件的共享等,是否也可以使用享元模式?

问题13:享元模式是否适用于所有场景?在某些情况下,是否有更合适的设计模式来替代享元模式?

享元模式适用于需要共享状态的场景,但如果对象的状态非常复杂,且每个对象的状态差异较大,是否可以考虑使用其他设计模式?例如,工厂模式、策略模式等,是否可能更适合一些场景?

接下来,我们将通过具体的代码示例来加深理解享元模式。

享元模式深入解读

一、引言

享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来有效地支持大量的细粒度对象。享元模式通过减少对象的创建数量来优化内存使用,尤其在需要创建大量相似对象时非常有用。该模式通过将相同的数据部分共享来减少冗余,从而节省内存,提高性能。


二、简单理解:什么是享元模式?

1. 什么是享元模式?

享元模式的核心思想是:通过共享已经存在的对象来减少内存的使用,尤其是当大量对象具有相同的状态时。享元模式把对象的状态分为两类:内部状态和外部状态。内部状态是对象的固定部分,可以共享;外部状态是对象的可变部分,不能共享。

通俗地讲,享元模式就像是你在使用一个图标库。当你需要多个相似的图标时,你并不为每个图标创建一个新的实例,而是共享相同的图标对象,只有在需要时,才为每个图标设置不同的位置(外部状态)。这样做不仅节省了内存,也提高了性能。

2. 享元模式的组成部分

享元模式通常包含以下几个部分:

  • 享元接口(Flyweight):定义共享对象的接口,通常包括设置和获取外部状态的方法。

  • 具体享元类(ConcreteFlyweight):实现享元接口,并存储共享的内部状态。

  • 享元工厂类(FlyweightFactory):负责创建并管理享元对象,确保共享对象的唯一性。

  • 外部状态(Extrinsic State):不需要共享的状态,通常由客户端来管理。


三、用自己的话解释:如何理解享元模式?

1. 类比实际生活中的场景

假设你有很多相似的产品(例如,多个相同型号的手机),每个手机的外观和功能是相同的,但它们可能具有不同的颜色、存储容量等特征。你并不需要为每个手机实例创建一个新的外观对象,而是可以共享相同的外观对象,只为每个手机单独存储颜色和容量等信息(外部状态)。通过这种方式,你可以减少内存使用,提高系统的效率。

在编程中,享元模式通过共享对象的内部状态,避免了重复创建相似的对象,并通过外部状态为每个对象提供个性化的特征,从而节省了内存。

2. 为什么要使用享元模式?

使用享元模式的好处是,它通过共享对象来减少内存使用,尤其是在需要大量相似对象的情况下,享元模式能够显著提高系统的性能。它将不可共享的部分和可共享的部分分开,确保只有外部状态是可变的,而共享对象的内部状态是固定的。


四、深入理解:享元模式的实现

接下来,我们通过一个具体的代码示例来实现享元模式,帮助你更好地理解如何在代码中使用这个模式。

示例:文字绘制系统

假设我们正在开发一个文字绘制系统,其中有多个相同字体和大小的文字对象,但它们的颜色和位置可能不同。我们可以通过享元模式来共享相同的文字对象,避免为每个文字都创建一个新的对象,从而节省内存。

1. 定义享元接口
# 享元接口:定义共享对象的接口
class Flyweight:def draw(self, color: str, position: tuple):pass
2. 定义具体享元类
# 具体享元类:表示一个共享的文字对象
class ConcreteFlyweight(Flyweight):def __init__(self, font: str, size: int):self.font = fontself.size = sizedef draw(self, color: str, position: tuple):print(f"Drawing text with font {self.font}, size {self.size}, color {color}, at position {position}")
3. 定义享元工厂类
# 享元工厂类:负责创建并管理享元对象
class FlyweightFactory:def __init__(self):self._flyweights = {}def get_flyweight(self, font: str, size: int):key = f"{font}-{size}"if key not in self._flyweights:print(f"Creating new Flyweight object for font {font} and size {size}")self._flyweights[key] = ConcreteFlyweight(font, size)return self._flyweights[key]
4. 客户端代码:使用享元模式绘制文字
# 客户端代码:使用享元模式绘制多个文字对象
factory = FlyweightFactory()# 获取共享的文字对象
text1 = factory.get_flyweight("Arial", 12)
text2 = factory.get_flyweight("Arial", 12)  # 这将复用之前的对象
text3 = factory.get_flyweight("Times New Roman", 14)# 绘制文字,设置不同的外部状态(颜色和位置)
text1.draw("Red", (10, 20))
text2.draw("Blue", (15, 25))
text3.draw("Green", (30, 35))# 输出:
# Creating new Flyweight object for font Arial and size 12
# Drawing text with font Arial, size 12, color Red, at position (10, 20)
# Drawing text with font Arial, size 12, color Blue, at position (15, 25)
# Creating new Flyweight object for font Times New Roman and size 14
# Drawing text with font Times New Roman, size 14, color Green, at position (30, 35)
代码解析:
  1. Flyweight 类:这是享元接口,定义了所有共享对象的公共方法。所有具体的享元类都必须实现这个接口,并定义如何绘制文字。

  2. ConcreteFlyweight 类:这是具体的享元类,表示具有固定字体和大小的文字对象。它实现了 draw 方法,根据外部状态(如颜色和位置)来绘制文字。

  3. FlyweightFactory 类:这是享元工厂类,负责创建和管理享元对象。工厂类缓存已经创建的享元对象,避免重复创建相同的对象。当客户端请求某个特定字体和大小的文字对象时,工厂会检查是否已有该对象,如果有则返回已存在的对象。

  4. 客户端代码:客户端通过享元工厂获取共享的文字对象,然后设置不同的外部状态(颜色和位置)来绘制文字。多个相同字体和大小的文字对象共享同一个享元对象,只在颜色和位置等外部状态上有所不同。


五、解释给别人:如何讲解享元模式?

1. 用简单的语言解释

享元模式就像是你有一堆相似的物品(例如多个相同的图标、文字等),而你不需要为每个物品创建一个新的实例。你可以通过共享相同的物品对象,只为每个物品设置不同的特征(外部状态)。这样,你可以节省大量内存,避免重复创建相似的对象。

2. 为什么要使用享元模式?

使用享元模式的好处是,它可以显著减少内存使用,尤其是在需要创建大量相似对象的情况下。通过共享对象的内部状态,只有在需要的时候,才为每个对象设置不同的外部状态,从而节省了资源。此外,享元模式也提高了系统的性能,特别是在图形界面和游戏开发等需要大量重复对象的场景中。


六、总结

享元模式通过共享不可变的状态,显著减少了内存开销,并提高了系统的效率,尤其在大量相似对象的场景中。然而,享元模式也可能增加客户端的复杂性,尤其是当外部状态需要管理时。

通过以上学习过程,我们可以得出以下结论:

  • 享元模式 通过共享相同的对象来减少内存使用,尤其是在需要大量相似对象时非常有用。它将对象的内部状态和外部状态分开,共享内部状态,外部状态由客户端管理。

  • 享元模式适用于那些需要大量相似对象且对象状态可分为共享和非共享部分的场景,如图形界面中的图标、文字、粒子等。

享元模式的优点:
  • 节省内存:通过共享相同的对象,减少了内存的使用。

  • 提高性能:减少了对象的创建和销毁,提高了系统的性能。

  • 灵活性:通过将内部状态和外部状态分离,使得对象的状态管理更加灵活。

享元模式的缺点:
  • 增加复杂性:享元模式引入了共享对象和外部状态的管理,可能导致代码的复杂度

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

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

相关文章

java方法重写学习笔记

方法重写介绍 子类和父类有两个返回值,参数,名称都一样的方法, 子类的方法会覆盖父类的方法。 调用 public class Overide01 {public static void main(String[] args) {Dog dog new Dog();dog.cry();} }Animal类 public class Animal {…

什么是ESLint?它有什么作用?

ESLint 是一个用于 静态代码分析 的工具,专门检测 JavaScript/TypeScript 代码中的潜在问题和风格违规。它通过预定义的规则集帮助开发者保持代码的一致性和质量,是前端工程化的核心工具之一。 一、ESLint 的核心作用 1. 错误检查(Error Detection) 识别语法错误、未定义变…

Docker的网络介绍

网络简单介绍 在介绍 Docker 的网络模式之前,先简单说下我们在使用 Vmware 虚拟机中的网络模式,形成对比,更好理解。 1、Vmware 中的网络模式 1.1、VMnet0(桥接模式) 虚拟机通过宿主机的物理网卡直接连接到外部网络…

Netty学习专栏(六):深度解析Netty核心参数——从参数配置到生产级优化

文章目录 前言一、核心参数全景解析1.1 基础网络层参数1.2 内存管理参数1.3 水位线控制1.4 高级参数与系统级优化 二、生产级优化策略2.1 高并发场景优化2.2 低延迟场景优化 总结 前言 在分布式系统和高并发场景中,Netty作为高性能网络通信框架的核心地位无可替代。…

计算机网络学习(六)——UDP

一、UDP UDP(User Datagram Protocol,用户数据报协议)是传输层的一种协议,和 TCP 并列。与 TCP 不同,UDP 是无连接、不可靠、面向报文的协议,它的设计目标是追求更快的数据传输速度和更小的开销。 UDP 为…

vue3文本超出三行显示省略号,点击查看更多显示全部文本

只有一行时&#xff08;不显示展开按钮&#xff09;&#xff1a; 话不多说&#xff0c;上码 ~template <el-col :span"24"><el-form-item :label"$t(warningOrgNames_)"><div class"content-box" ref"contanierRef"…

手写Tomcat(一)

一、Tomcat简介 Tomcat 服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP 程序的首选。 1.1 Tomcat基本架构 Servlet接口文件中定义的方法有以下…

第三节_PySide6中Qt Designer 的基础使用_上篇

文章目录 前言一、Qt Designer简介1.什么是 Qt Designer&#xff1f;2.核心功能3.核心优势 二、Qt Designer界面介绍1.主窗口的创建2.窗口五大区域的简单介绍 三、界面布局 Layout1.窗口布局方式介绍2.UI布局技巧概述3.UI布局实战应用 总结 前言 第二节_PySide6项目创建流程介…

行列式的线性性质(仅限于单一行的加法拆分)

当然可以&#xff0c;以下是经过排版优化后的内容&#xff0c;保持了原始内容不变&#xff0c;仅调整了格式以提升可读性&#xff1a; 行列式的线性性质&#xff08;加法拆分&#xff09; 这个性质说的是&#xff1a;如果行列式的某一行&#xff08;或某一列&#xff09;的所有…

Git使用说明

配置Git 确定已经安装了Git, 通过以下的命令配置全局的邮箱和用户名 git config --global user.email "your@xx.com" git config --global user.name "yourname" 初始化本地仓库 首先,打开终端并切换到存放你代码的项目目录。接着执行以下命令,将该…

【后端高阶面经:缓存篇】36、如何保证Redis分布式锁的高可用和高性能?

一、分布式锁核心挑战:从单机到分布式的跨越 (一)分布式锁的本质需求 互斥性:同一时刻仅一个客户端持有锁容错性:节点故障时锁仍有效(避免单点)原子性:加锁/释放锁操作原子完成可重入性:支持同一客户端多次获取同一把锁(二)Redis天然优势 单线程模型保证操作原子性…

【后端高阶面经:MongoDB篇】40、怎么优化MongoDB的查询性能?

一、索引优化&#xff1a;构建高效查询的基石 &#xff08;一&#xff09;索引类型与适用场景 1. 五大核心索引类型 索引类型适用场景示例代码性能影响单字段索引单条件查询&#xff08;如用户ID、状态字段&#xff09;db.users.createIndex({ user_id: 1 })低复合索引多条件…

Linux wget 常用命令详解

目录 1.1 工具定位 基础下载示例 二、高效下载参数详解 2.1 下载控制类 2.2 文件管理类 2.3 网络优化类 三、高级应用场景 3.1 递归下载与整站镜像 3.2 自动化下载实践 3.3 安全下载配置 四、参数速查手册 4.1 常用参数汇总 1.1 工具定位 基础下载语法 wget [选项…

Pytorch中文文本分类

本文为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊 将对中文文本进行分类&#xff0c;示例如下&#xff1a; 文本分类流程图 1.加载数据 import time import pandas as pd import torch from torch.utils.data import DataLoader, random_split impo…

13.「极简」扣子(coze)教程 | 小程序UI设计进阶(三)让界面动起来,实操讲透“聚焦”事件

前一期大师兄介绍了扣子平台组件的两种状态“禁用”和“加载”。这两种方法使控件可以通过简单设置表示出更多的运行状态。今天大师兄将详细介绍控件的一种事件“聚焦”。 扣子&#xff08;coze&#xff09;编程 「极简」扣子(coze)教程 | 小程序UI设计进阶 II&#xff01;让…

剑指offer11_矩阵中的路径

矩阵中的路径 请设计一个函数&#xff0c;用来判断在一个矩阵中是否存在一条路径包含的字符按访问顺序连在一起恰好为给定字符串。 路径可以从矩阵中的任意一个格子开始&#xff0c;每一步可以在矩阵中向左&#xff0c;向右&#xff0c;向上&#xff0c;向下移动一个格子。 如…

腾讯2025年校招笔试真题手撕(三)

一、题目 今天正在进行赛车车队选拔&#xff0c;每一辆赛车都有一个不可以改变的速度。现在需要选取速度差距在10以内的车队&#xff08;车队中速度的最大值减去最小值不大于10&#xff09;&#xff0c;用于迎宾。车队的选拔按照的是人越多越好的原则&#xff0c;给出n辆车的速…

《三维点如何映射到图像像素?——相机投影模型详解》

引言 以三维投影介绍大多比较分散&#xff0c;不少小伙伴再面对诸多的坐标系转换中容易弄混&#xff0c;特别是再写代码的时候可能搞错&#xff0c;所有这篇文章帮大家完整的梳理3D视觉中的投影变换的全流程&#xff0c;一文弄清楚这个过程&#xff0c;帮助大家搞清坐标系转换…

Ini配置文件读写,增加备注功能

1.增加备注项写入 例: #节点备注 [A] #项备注 bbb1 ccc2 [B] bbb1 IniConfig2 ic new IniConfig2(); //首次写入 if (!ic.CanRead()) { ic.AddSectionReMarke("A", "节点备注"); ic.SetValue("A&qu…

OpenHarmony 5.0中状态栏添加以太网状态栏图标以及功能实现

目录 1.前置条件 2.方案 1.前置条件 首先以太网接口是有问题的,如下按照如下流程将以太网接口进行修复 OpenHarmony 以太网卡热插拔事件接口无效-CSDN博客 然后上述的接口可以了就可以通过这个接口获取以太网是否连接状态 要注意wifi连接的干扰和预置虚拟网口干扰 2.方案…