设计模式-享元模式(Flyweight)

1. 概念

  • 享元模式是一种结构型设计模式,它通过共享技术有效地支持大量细粒度对象的复用。

2. 原理结构图

图1
在这里插入图片描述
图2
在这里插入图片描述

2. 1 角色

  • 抽象享元(Flyweight):这是所有具体享元类的基类,它定义了享元对象的内部状态和外部状态,以及需要实现的公共接口。外部状态通常以参数的形式通过方法传入。
  • 具体享元(Concrete Flyweight):这个角色实现了抽象享元中定义的接口,具体的享元类包含了实现细节,它们共享相同的内部状态,但可以根据需要存储或计算外部状态。
  • 非享元(Unsharable Flyweight):代表不可以共享的外部状态,这些状态是特定于上下文的,因此不能共享。它们以参数的形式注入到具体享元的相关方法中。
  • 享元工厂(Flyweight Factory):用于构建一个池容器,负责创建和管理享元对象。当客户端请求一个享元对象时,享元工厂会检查系统中是否已存在符合要求的享元对象,如果存在,则提供给客户;如果不存在,则创建一个新的享元对象

2. 2 内部状态和外部状态

**享元模式的内部状态和外部状态是模式的核心概念,**它们在实现对象共享和优化内存使用中起着关键作用。

  • 内部状态
    • 内部状态是享元对象的固有属性,这些属性不会随着外部环境的变化而改变。
    • 它是可以共享的状态,通常设计为不可变的属性,以便于多个享元对象之间共享。
    • 例如,在一个文档编辑器中,字体或颜色方案可以是内部状态,因为它们是共享的并且不会因为文档的不同部分而改变
  • 外部状态
    • 外部状态是随环境变化而变化的、不可以共享的状态。
    • 它通常是依赖于某个特定上下文的信息,比如对象的位置或者是与其他对象的临时关系。
    • 在连接池的例子中,连接的可用状态就是外部状态,因为它会随着连接是否被使用而改变,并且每个连接的可用状态是独立的,不能共享。
  • 举例
    • 假设我们有一个在线字体库,用户可以从字体库中选择字体,并设置字体的颜色和大小。在这个例子中,字体的字符集(如宋体、黑体等)就是内部状态,因为这是字体对象固有的属性,不随用户的操作而改变。而字体的颜色、大小等属性就是外部状态,因为这些属性会随着用户的选择而改变。

3. 代码示例

3.1 示例1
  • 使用享元模式实现一个字符渲染系统。在这个系统中,有很多字符需要渲染,但每个字符可能只是颜色或字体大小上有所不同,而字符的形状本身是一样的。因此,可以共享字符的形状,而只存储不同的颜色或字体大小信息。
// Flyweight 接口
interface Flyweight {void operation(int extrinsicState);
}// ConcreteFlyweight 类,实现 Flyweight 接口
class UncheckedFlyweight implements Flyweight {private final String name;public UncheckedFlyweight(String name) {this.name = name;}@Overridepublic void operation(int extrinsicState) {System.out.println("Displaying " + name + " with extrinsic state " + extrinsicState);}
}// FlyweightFactory 类,用于创建和管理 Flyweight 对象
class FlyweightFactory {private final Map<String, Flyweight> flyweights = new HashMap<>();public Flyweight getFlyweight(String key) {Flyweight flyweight = flyweights.get(key);if (flyweight == null) {flyweight = new UncheckedFlyweight(key);flyweights.put(key, flyweight);}return flyweight;}
}// Client 类,使用 Flyweight 对象
class Client {private final FlyweightFactory factory;public Client(FlyweightFactory factory) {this.factory = factory;}public void display(String key, int extrinsicState) {Flyweight flyweight = factory.getFlyweight(key);flyweight.operation(extrinsicState);}
}public class FlyweightPatternDemo {public static void main(String[] args) {// 创建 FlyweightFactory 实例FlyweightFactory factory = new FlyweightFactory();// 创建 Client 实例Client client = new Client(factory);// 使用 Client 显示字符,传入不同的外部状态client.display("A", 1); // 显示字符 A,使用外部状态 1(可能是红色)client.display("B", 2); // 显示字符 B,使用外部状态 2(可能是绿色)client.display("A", 3); // 再次显示字符 A,使用外部状态 3(可能是蓝色)}
}
  • 运行FlyweightPatternDemo类的main方法,将看到以下输出:
Displaying A with extrinsic state 1  
Displaying B with extrinsic state 2  
Displaying A with extrinsic state 3
  • 在这个例子中,尽管多次请求显示字符"A",但FlyweightFactory只创建了一个"A"字符对象,并在后续请求中重复使用了它。这减少了内存中的对象数量,提高了性能。同时,通过传入不同的外部状态,可以为同一个字符对象赋予不同的表现(如不同的颜色或字体大小)。

3.2 示例2
  • 有一个简单的图形渲染系统,其中有一些基础的图形(如圆形、矩形)可以共享,而每个图形实例可以有不同的颜色、大小等属性。
// Flyweight 接口,表示可共享对象
interface Flyweight {void draw();
}// UnsharedFlyweight 接口,表示不可共享对象
interface UnsharedFlyweight {void setExtrinsicState(int state);void operation();
}// ConcreteFlyweight 类,实现 Flyweight 接口
class ConcreteFlyweight implements Flyweight {private final String shape;public ConcreteFlyweight(String shape) {this.shape = shape;}@Overridepublic void draw() {System.out.println("Drawing " + shape);}
}// ConcreteUnsharedFlyweight 类,实现 UnsharedFlyweight 接口
class ConcreteUnsharedFlyweight implements UnsharedFlyweight {private int extrinsicState;@Overridepublic void setExtrinsicState(int state) {this.extrinsicState = state;}@Overridepublic void operation() {System.out.println("Operating on unshared flyweight with extrinsic state " + extrinsicState);}
}// FlyweightFactory 类,用于创建和管理 Flyweight 对象
class FlyweightFactory {private final Map<String, Flyweight> flyweights = new HashMap<>();public Flyweight getFlyweight(String key) {Flyweight flyweight = flyweights.get(key);if (flyweight == null) {flyweight = new ConcreteFlyweight(key);flyweights.put(key, flyweight);}return flyweight;}
}class GraphicsSystem {private final FlyweightFactory flyweightFactory;private UnsharedFlyweight unsharedFlyweight;public GraphicsSystem(FlyweightFactory flyweightFactory) {this.flyweightFactory = flyweightFactory;}public void setUnsharedFlyweight(UnsharedFlyweight unsharedFlyweight) {this.unsharedFlyweight = unsharedFlyweight;}public void drawShape(String shapeKey, int extrinsicState) {Flyweight flyweight = flyweightFactory.getFlyweight(shapeKey);flyweight.draw();if (unsharedFlyweight != null) {unsharedFlyweight.setExtrinsicState(extrinsicState);unsharedFlyweight.operation();}}
}public class FlyweightPatternDemo {public static void main(String[] args) {// 创建 FlyweightFactory 实例FlyweightFactory flyweightFactory = new FlyweightFactory();// 创建 GraphicsSystem 实例GraphicsSystem graphicsSystem = new GraphicsSystem(flyweightFactory);// 创建非共享的 UnsharedFlyweight 对象UnsharedFlyweight unsharedFlyweight = new ConcreteUnsharedFlyweight();graphicsSystem.setUnsharedFlyweight(unsharedFlyweight);// 使用 GraphicsSystem 绘制不同的形状,并设置非共享对象的状态graphicsSystem.drawShape("Circle", 1); // 绘制圆形,设置非共享对象状态为 1graphicsSystem.drawShape("Rectangle", 2); // 绘制矩形,设置非共享对象状态为 2graphicsSystem.drawShape("Circle", 3); // 再次绘制圆形,设置非共享对象状态为 3}
}
  • 当运行main方法时,会看到以下输出:
Drawing Circle  
Operating on unshared flyweight with extrinsic state 1  
Drawing Rectangle  
Operating on unshared flyweight with extrinsic state 2  
Drawing Circle  
Operating on unshared flyweight with extrinsic state 3
  • 在这个输出中,可以看到:
    • 对于圆形和矩形,由于它们是可共享的,所以FlyweightFactory只创建了一个实例,并在后续的调用中返回了相同的实例。
    • 对于非共享对象,每次调用drawShape方法时都设置了不同的状态,并进行了操作。
  • 这个示例展示了享元模式在资源受限的情况下如何有效地重用对象,同时保留了处理特定外部状态或行为的能力。通过区分共享和非共享对象,可以在提高性能和减少内存消耗的同时保持代码的灵活性和扩展性。

4. 优缺点

  • 主要作用
    • 通过共享对象来减少内存使用和提高性能。
  • 优点
    • 减少内存消耗:通过共享对象,可以显著减少系统中对象的数量,从而节省内存空间。这在处理大量相似对象时尤为有效,如数据库连接池、线程池等场景。
    • 提高系统性能:由于减少了对象的创建和销毁,享元模式可以提高系统的响应速度和性能。在高并发场景下,如电商网站的购物车或在线游戏的排行榜,这种性能提升尤为显著。
    • 支持对象共享:通过将对象的状态分为内部状态和外部状态,享元模式可以实现对象的可共享性。这使得多个对象可以共享相同的内部状态,而保持各自独立的外部状态。
  • 缺点
    • 实现较为复杂:享元模式需要将对象的状态分为内部状态和外部状态,这可能需要额外的设计和编程工作。同时,使用工厂类来管理共享对象也可能增加实现的复杂性。
    • 可能影响系统维护性:将对象状态分为内部和外部状态可能会增加系统的复杂度,使得系统的维护和理解变得更加困难。此外,如果过度使用享元模式,可能会导致代码的可读性和可维护性下降。
    • 可能降低代码可读性:由于享元模式使用工厂类来管理共享对象,可能会增加代码的抽象层次和间接性,从而降低代码的可读性。对于不熟悉该模式的开发人员来说,理解和维护代码可能会更加困难。

5. 应用场景

5.1 主要包括以下几个方面
  1. 大量共享对象的场景:在系统中存在大量共享对象时,如数据库连接池、线程池等,可以通过享元模式实现对象的共享,减少对象的创建和销毁,提高系统的性能和可扩展性。
  2. 大数据量的场景:处理大量数据的系统中,往往存在大量重复对象,如图像处理中的像素点、文本处理中的单词等。这些对象可以通过享元模式进行共享,减少对象的创建和内存消耗,提高系统的性能和可扩展性。
  3. 高并发的场景:高并发系统如电商网站的购物车、在线游戏的排行榜等,存在大量的请求和对象。通过享元模式共享这些对象,可以减少对象的创建和销毁,从而提高系统的并发处理能力和响应速度。
  4. 分布式系统中的对象共享:在分布式系统中,存在大量对象需要在不同节点间共享,如分布式缓存系统、分布式锁等。享元模式可以有效地支持这些对象在分布式环境中的共享。

5.2 实际应用
  1. 五子棋游戏:在五子棋游戏中,可以使用享元模式来表示棋子。由于棋盘上可能存在大量重复的棋子(例如黑棋或白棋),享元模式可以通过共享相同的棋子对象来减少内存占用。在这个场景中,棋子类可以作为抽象享元角色,而具体的颜色实例(如黑棋或白棋)则是具体享元角色。
  2. 文档编辑器:在文本编辑器或处理软件中,可以使用享元模式来管理文本中的字符。由于同一个文档中可能包含大量重复的字符,使用享元模式可以有效地减少内存消耗。在这种情况下,字符类是抽象享元角色,而具体的字符实例(如’a’、‘b’、'c’等)是具体享元角色。
  3. 网页开发:在网页开发中,可以使用享元模式来管理图标、图片或其他重复使用的图形元素。这有助于减少页面加载时间,并降低服务器的带宽需求。
  4. 网络游戏:在多人网络游戏中,可以使用享元模式来管理游戏中的角色、道具等资源。这有助于减少服务器的资源消耗,并提高游戏的可伸缩性。
  5. 电子商务平台:在电子商务平台中,可以使用享元模式来管理商品图片、评价等级图标等元素。这有助于减少页面加载时间,并提高用户体验。

6. JDK中的使用

  • 字符串池(String Pool):Java中的String类使用了一个内部的字符串池来管理字符串对象。当您创建一个新的String对象时,如果相同的值已经存在于池中,那么新的引用将指向池中的现有对象,而不是创建一个新的对象。这通过String类的intern()方法实现,该方法确保了相同内容的字符串共享同一个对象。
  • Integer缓存:对于整数类型的自动装箱操作,Java内部维护了一个小的缓存(通常是-128到127之间的整数),当进行自动装箱时,如果整数值在这个范围内,将直接返回缓存中的对象,而不是创建新的对象。
  • Boolean值:类似于Integer,Boolean类型的true和false在自动装箱时也是直接返回缓存中的实例,不会创建新对象。

7. 注意事项

  1. 区分内外部状态:确保对象的内部状态是共享的,而外部状态是从外部传入的。内部状态是共享的关键,而外部状态则是每个对象特有的。
  2. 管理共享对象:使用工厂模式来管理享元对象,确保它们被正确地创建和复用。
  3. 线程安全:在多线程环境中,要注意线程安全问题,避免共享对象的状态被意外修改。
  4. 节省内存:享元模式通过复用对象来节省内存,这在处理大量对象时尤其重要。
  5. 理解概念:“享”代表共享,“元”代表对象。当系统中有大量对象消耗大量内存时,可以考虑使用享元模式。
  6. 不变属性存储:享元对象应只存储不变的共享属性,可变属性应由非享元类存储,并通过享元工厂进行管理。

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

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

相关文章

C语言中生成随机数的几种方式

一.rand 1.rand介绍 C语言提供了一个函数叫rand&#xff0c;这函数是可以生成随机数的&#xff0c;函数原型如下所示&#xff1a; int rand (void); rand函数会返回一个伪随机数&#xff0c;这个随机数的范围是在0~RAND_MAX之间&#xff0c;这个RAND_MAX的大小是依赖编译器…

java快速幂算法

快速幂算法 参考视频(参考五角七边up大佬&#xff09; 幂运算的介绍 幂运算是指将一个数自身乘以自身多次的运算&#xff0c;其表达式为 a n a^n an&#xff0c;其中 a a a 是底数&#xff0c; n n n 是指数。 快速幂解释 快速幂算法是一种用于快速计算幂运算的算法&…

[当人工智能遇上安全] 13.威胁情报实体识别 (3)利用keras构建CNN-BiLSTM-ATT-CRF实体识别模型

《当人工智能遇上安全》系列将详细介绍人工智能与安全相关的论文、实践&#xff0c;并分享各种案例&#xff0c;涉及恶意代码检测、恶意请求识别、入侵检测、对抗样本等等。只想更好地帮助初学者&#xff0c;更加成体系的分享新知识。该系列文章会更加聚焦&#xff0c;更加学术…

Unity中支持泰语--没有版权限制

在Unity中支持泰语主要涉及以下几个方面&#xff1a; 选择合适的字体&#xff1a;在Unity中&#xff0c;确保使用支持泰文字符的字体是至关重要的。例如&#xff0c;可以选择使用Noto Serif Thai字体&#xff0c;这是一个支持泰语的字体2。 处理Unity版本问题&#xff1a;某些…

CentOS 网卡ifcfg-eth0 ping不通外网(www.baidu.com)

1、如果确认好就直接激活网卡&#xff01; ifup eth0 2、慢慢找&#xff1a; cd /etc/sysconfig/network-scripts/ ls 找到你的网卡是啥&#xff0c;这里网卡是 ifcfg-eth0 执行1就好了&#xff01;

JetBrains PyCharm 2024.1 发布 - 面向专业开发者的 Python IDE

JetBrains PyCharm 2024.1 发布 - 面向专业开发者的 Python IDE 请访问原文链接&#xff1a;JetBrains PyCharm 2024.1 (macOS, Linux, Windows) - 面向专业开发者的 Python IDE&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org…

LINUX[网络编程]

sendto函数的应用&#xff1a;向to结构体指针指向的IP&#xff0c;发送UDP数据 细节请看我注释&#xff0c;注满细节 代码&#xff1a; #include <stdio.h> #include <sys/socket.h> //socket函数 #include <unistd.h> //close函数 #include <st…

「PHP系列」PHP表单及表单验证详解

文章目录 一、表单二、表单校验三、相关链接 一、表单 PHP 表单用于收集用户输入的数据&#xff0c;并将这些数据发送到服务器进行处理。在 PHP 中&#xff0c;通常使用 HTML 表单来收集用户输入&#xff0c;然后通过 PHP 脚本处理这些数据。 <!DOCTYPE html> <html…

acwing2060. 奶牛选美

题目&#xff1a; 代码&#xff1a; //acwing2060. 奶牛选美 #include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N55; const int dx[]{-1,0,1,0},dy[]{0,-1,0,1}; bool st[N][N]; int point[N][N]; char map[N][…

Go实现简单的协程池(通过channel实现)

go编程时&#xff0c;goroutine是非常有用的特性。然而&#xff0c;实践中最好不要无限制的使用goroutine&#xff0c;例如一次性开一万个goroutine去读写文件是很危险的。为了控制goroutine的并行量&#xff0c;有很多框架或库实现了协程池&#xff0c;例如ants&#xff08;很…

MYBATIS获取参数值

MYBATIS最核心的莫过于动态的获取各种的参数值, 为了将来更好的使用MYBATIS进行开发, 我们必须先打好 "获取参数值" 这一基础 一. MYBATIS获取参数值的两种情况: 1.${} 实质:字符串的拼接 注解:${}使用的字符串拼接的方式拼接SQL语句, 所以, 如果其中出现了字符串…

APP下载页前端自适应HTML源码

源码介绍 APP下载页前端自适应HTML源码&#xff0c;可以作为自己的软件介绍页或者app下载页&#xff0c;喜欢的朋友可以拿去研究 效果预览 HTML源码下载 https://www.qqmu.com/3026.html

Flink WordCount实践

目录 前提条件 基本准备 批处理API实现WordCount 流处理API实现WordCount 数据源是文件 数据源是socket文本流 打包 提交到集群运行 命令行提交作业 Web UI提交作业 上传代码到gitee 前提条件 Windows安装好jdk8、Maven3、IDEA Linux安装好Flink集群&#xff0c;可…

线上问题监控 Sentry 接入全过程

背景&#xff1a; 线上偶发问题出现后 &#xff0c;测试人员仅通过接口信息无法复现错误场景&#xff1b;并且线上环境的监控&#xff0c;对于提高系统的稳定性 &#xff08;降低脱发率&#xff09; 至关重要&#xff1b;现在线上监控工具这个多&#xff0c;为什么选择Sentry?…

Java并发(1)--线程,进程,以及缓存

线程和进程是什么&#xff1f; 进程 进程是程序的一次执行过程&#xff0c;系统程序的基本单位。有自己的main方法&#xff0c;并且主要由主方法运行起来的基本上就是进程。 线程 线程与进程相似&#xff0c;但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以…

vb.net textbox滚动显示到最后一行

调用&#xff1a; Private Sub TextBox18_TextChanged(sender As Object, e As System.EventArgs) Handles TextBox18.TextChanged show_textbox_endline(TextBox18) End Sub 函数&#xff1a; 显示textbox最后一行 Public Sub show_textbox_endline(Tbx As TextB…

MVCC(解决MySql中的并发事务的隔离性)

MVCC 如何保证事务的隔离性&#xff1f; 1.排他锁&#xff1a;如一个事务获取了一个数据行的排他锁&#xff0c;其他事务就不能再获取改行的其他锁。 2.MVCC&#xff1a;多版本并发控制。 MVCC&#xff1a; 1.隐藏字段 1.DB_TRX_ID&#xff1a;最近修改事务的id。默认值从0开…

Selenium自动填写验证码(偏小白版本OCR)

OCR基础示例 我直接 上代码 from PIL import Image import pytesseract# 0 Orientation and script detection (OSD) only. # 1 Automatic page segmentation with OSD. # 2 Automatic page segmentation, but no OSD, or OCR. # 3 Fully automatic page segmentation, but n…

【MYSQL】索引机制概述

由于MySQL是作为存储层部署在业务系统的最后端&#xff0c;所有的业务数据最终都要入库落盘&#xff0c;但随着一个项目在线上运行的时间越来越久&#xff0c;数据库中的数据量自然会越来越多&#xff0c;而数据体积出现增长后&#xff0c;当需要从表查询一些数据时&#xff0c…

symfony框架

Symfony框架是一种流行的PHP框架&#xff0c;用于快速开发高质量的Web应用程序。它是一个开源框架&#xff0c;遵循MVC&#xff08;模型-视图-控制器&#xff09;设计模式&#xff0c;提供了一套强大的工具和组件&#xff0c;帮助开发人员更轻松地构建复杂的Web应用程序。 Sym…