设计模式-享元模式在Java中的使用示例-围棋软件

场景

享元模式

简介

当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题。

例如在一个文本字符串中存在很多重复的字符,如果每一个字符都用一个单独的对象来表示,将会占用

较多的内存空间,那么我们如何去避免系统中出现大量相同或相似的对象,同时又不影响客户端程序。

通过面向对象的方式对这些对象进行操作?

享元模式正为解决这一类问题而诞生。享元模式通过共享技术实现相同或相似对象的重用,

在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,

这个对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例,在享元模式中,

存储这些共享实例对象的地方称为享元池(Flyweight Pool)。我们可以针对每一个不同的字符创建一个享元对象,

将其放在享元池中,需要时再从享元池取出。

享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部

状态(Intrinsic State)和外部状态(Extrinsic State)。

下面将对享元的内部状态和外部状态进行简单的介绍:

(1) 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。如字符的内容,

不会随外部环境的变化而变化,无论在任何环境下字符“a”始终是“a”,都不会变成“b”。

(2) 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,

并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态

之间是相互独立的。如字符的颜色,可以在不同的地方有不同的颜色,例如有的“a”是红色的,

有的“a”是绿色的,字符的大小也是如此,有的“a”是五号字,有的“a”是四号字。而且字符的颜色和大

小是两个独立的外部状态,它们可以独立变化,相互之间没有影响,客户端可以在使用时将外部状态

注入享元对象中。

正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中

的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对

象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。

享元模式结构图

 

享元模式角色

Flyweight(抽象享元类):

通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),

同时也可以通过这些方法来设置外部数据(外部状态)。

ConcreteFlyweight(具体享元类):

它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,

为每一个具体享元类提供唯一的享元对象。

UnsharedConcreteFlyweight(非共享具体享元类):

并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;

当需要一个非共享具体享元类的对象时可以直接通过实例化创建。

FlyweightFactory(享元工厂类):

享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,

享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;

当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),

返回新创建的实例并将其存储在享元池中。

在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,

首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。

应用实例

通过对围棋软件进行分析,发现在围棋棋盘中包含大量的黑子和白子,它们的形状、大小都一模一样,只是出现的位置不同而已。

如果将每一个棋子都作为一个独立的对象存储在内存中,将导致该围棋软件在运行时所需内存空间较大,

如何降低运行代价、提高系统性能是一个问题。为了解决这个问题,决定使用享元模式来设计该围棋软件的棋子对象。

IgoChessman充当抽象享元类,BlackIgoChessman和WhiteIgoChessman充当具体享元类,IgoChessmanFactory充当享元工厂类。

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主

实现

1、新建围棋棋子类作为抽象享元类

//围棋棋子类:抽象享元类
abstract class IgoChessman {public abstract String getColor();public void display(Coordinates coordinates){System.out.println("棋子颜色:"+this.getColor()+",棋子位置:"+coordinates.getX()+coordinates.getY());}
}

这里棋子的位置作为外部状态。

附坐标位置类

//坐标类:外部状态类
public class Coordinates {private int x;private int y;public Coordinates(int x, int y) {this.x = x;this.y = y;}public int getX() {return x;}public void setX(int x) {this.x = x;}public int getY() {return y;}public void setY(int y) {this.y = y;}
}

2、新建黑色棋子类作为具体享元类

//黑色旗子类:具体享元类
public class BlackIgoChessman extends IgoChessman{@Overridepublic String getColor() {return "黑色";}
}

3、新建白色棋子类作为具体享元类

//白色旗子类:具体享元类
public class WhiteIgoChessman extends IgoChessman{@Overridepublic String getColor() {return "白色";}
}

4、新建围棋棋子工厂类作为享元工厂类,使用单例模式进行设计

//围棋棋子工厂类:享元工厂类,使用单例模式进行设计
public class IgoChessmanFactory {private static IgoChessmanFactory instance = new IgoChessmanFactory();//使用Hashtable来存储享元对象,充当享元池private static Hashtable ht;private IgoChessmanFactory(){ht = new Hashtable();IgoChessman black,white;black = new BlackIgoChessman();ht.put("b",black);white = new WhiteIgoChessman();ht.put("w",white);}//返回享元工程类的唯一实例public static IgoChessmanFactory getInstance(){return instance;}//通过key来获取存储在Hashtable中的享元对象public static IgoChessman getIgoChessman(String color){return (IgoChessman) ht.get(color);}
}

5、客户端调用示例

public class Client {public static void main(String[] args) {IgoChessman black1,black2,black3,white1,white2;IgoChessmanFactory factory;//获取享元工厂对象factory = IgoChessmanFactory.getInstance();//通过享元工厂获取三颗黑子black1 = factory.getIgoChessman("b");black2 = factory.getIgoChessman("b");black3 = factory.getIgoChessman("b");System.out.println("判断两颗黑子是否相同:"+(black1 == black2));//通过享元工厂获取两颗白子white1 = factory.getIgoChessman("w");white2 = factory.getIgoChessman("w");System.out.println("判断两颗白子是否相同:"+(white1 == white2));//显示棋子black1.display(new Coordinates(1,1));black2.display(new Coordinates(2,1));black3.display(new Coordinates(3,2));white1.display(new Coordinates(4,3));white2.display(new Coordinates(5,2));}
}

6、总结

当系统中存在大量相同或者相似的对象时,享元模式是一种较好的解决方案,它通过共享技术实现相

同或相似的细粒度对象的复用,从而节约了内存空间,提高了系统性能。相比其他结构型设计模式,

享元模式的使用频率并不算太高,但是作为一种以“节约内存,提高性能”为出发点的设计模式,它在

软件开发中还是得到了一定程度的应用。

享元模式的主要优点如下:

(1) 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。

(2) 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

主要缺点

(1) 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。

(2) 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。

适用场景

(1) 一个系统有大量相同或者相似的对象,造成内存的大量耗费。

(2) 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

(3) 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,

因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

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

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

相关文章

【案例教程】基于Python机器学习、深度学习技术提升气象、海洋、水文领域实践应用能力

Python是功能强大、免费、开源,实现面向对象的编程语言,能够在不同操作系统和平台使用,简洁的语法和解释性语言使其成为理想的脚本语言。除了标准库,还有丰富的第三方库,Python在数据处理、科学计算、数学建模、数据挖…

Vue中值的传递(父传子,子传父,子父同步)

1.父组件->子组件传递数据 ①父组件通过 v-bind: 属性绑定的形式,把数据传递给子组件 如果不需要动态绑定,则可以直接写number“张三” ②子组件中,通过props接收父组件传递过来的数据 2.子组件->父组件传递数据 1.在子组件中&#xf…

实现外部缓存-Redis

目录 实现 RedisTemplate RedisTemplate的序列化 RedisSerializer 创建Redis缓存配置类 测试使用 创建配置类 创建注解测试实体 创建配置文件 创建单元测试类进行测试 实现 RedisTemplate XXXTemplate 是 Spring 的一大设计特色,其中,RedisTe…

【基础算法】——双指针算法

文章目录 一、算法原理二、算法实战1. 力扣283 移动零2. 力扣1089 复写零3. 力扣15 三数之和4. 力扣18 四数之和 三、总结 一、算法原理 双指针算法是指在遍历对象的过程中不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(…

Tomcat服务器下载安装及配置教程(IDEA中使用Tomcat)

目录 友情提醒第一章、Tomcat下载与安装1.1)Tomcat介绍1.2)官网下载 第二章、Tomcat配置环境变量2.1)windows环境变量配置2.2)验证Tomcat配置是否成功2.3)报错解决 第三章、IDEA整合Tomcat3.1)打开IDEA开发…

【深度学习笔记】随机梯度下降法

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记,视频由网易云课堂与 deeplearning.ai 联合出品,主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习,视频的链接如下: 神经网络和…

springboot项目创建整个完成过程和注意事项

1:application.yml文件配置 server:port: 8088servlet:context-path: /test spring:datasource:name: text #????url: jdbc:mysql://localhost:3306/dsdd?serverTimezoneGMT&useUnicodetrue&characterEncodingutf-8&useSSLtrueusername: root #…

Rust 数据类型 之 结构体(Struct)

目录 结构体(Struct) 定义与声明 结构体定义 结构体实例 结构体分类 单元结构体(Unit Struct) 元组结构体(Tuple Struct) 具名结构体(Named Struct) 结构体嵌套 结构体方法…

【后端面经】前言汇总(0)

文章目录 一、机会是留给有准备的人二、课程设计第一部分:微服务架构第二部分:数据库与 MySQL第三部分:消息队列第四部分:缓存所谓缓存用得好,性能没烦恼。第五部分:NoSQL三、总结一、机会是留给有准备的人 近两年互联网行业增速放缓,ChatGPT 又引发了一波新的 AI 浪潮,…

使用ffmpeg合并视频遇到的坑

下面以Linux环境介绍为主 1.ffmpeg可执行命令不同的环境是不同的,Linux在执行命令前还需要授权。 2.合并视频命令: 主要命令: {} -f concat -auto_convert 0 -safe 0 -i {} -y -c:v copy 坑一:其中第一个花括号替换的是可执行命令所在的…

【GitOps系列】使用Kustomize和Helm定义应用配置

文章目录 使用 Kustomize 定义应用改造示例应用1.创建基准和多环境目录2.环境差异分析3.为 Base 目录创建通用 Manifest4.为开发环境目录创建差异 Manifest5.为预发布环境创建差异 Manifest6.为生产环境创建差异 Manifest 部署 Kustomize 应用部署到开发环境部署到生产环境 使用…

OpenCv (C++) 使用矩形 Rect 覆盖图像中某个区域

文章目录 1. 使用矩形将图像中某个区域置为黑色2. cv::Rect 类介绍 1. 使用矩形将图像中某个区域置为黑色 推荐参考博客:OpenCV实现将任意形状ROI区域置黑(多边形区域置黑) 比较常用的是使用 Rect 矩形实现该功能,代码如下&…

打造i-SMART智能网联平台,亚马逊云科技助力上汽快速出海

当前在各大外资车企不断加码在华投资之际,越来越多的中国汽车品牌纷纷开始走出国门,加速推进全球化业务,将赛道转至更为广阔的海外市场。 上汽海外出行科技有限公司(简称“上汽海外出行”)成立于2018年,承…

linux高并发web服务器开发(web服务器)18_函数解析http请求, 正则表达式,sscanf使用,http中数据特殊字符编码解码

pdf详情版 01 学习目标 编写函数解析http请求 ○ GET /hello.html HTTP/1.1\r\n ○ 将上述字符串分为三部分解析出来编写函数根据文件后缀,返回对应的文件类型sscanf - 读取格式化的字符串中的数据 ○ 使用正则表达式拆分 ○ [^ ]的用法通过浏览器请求目录数据 ○…

【unity之IMGUI实践】单例模式管理数据存储【二】

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:uni…

每日一题——迷宫问题(I)

迷宫问题——I 题目链接 思路 创建二维数组,并实现输入 首先输入二维数组的行和列: int n, m; scanf("%d%d", &n, &m);然后动态开辟二维数组: 注:对动态开辟还不太了解的同学可以看看👉C语言—…

CPU密集型和IO密集型任务的权衡:如何找到最佳平衡点

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。 目录 一、导读二、概览三、CPU密集型与IO密集型3.1、CPU密集型3.2、I/O密…

opencv-15 数字水印原理

最低有效位(Least Significant Bit,LSB)指的是一个二进制数中的第 0 位(即最低位)。 最低有效位信息隐藏指的是,将一个需要隐藏的二值图像信息嵌入载体图像的最低有效位,即将载体图像的最低有效…

再开源一款轻量内存池

前两天已开源线程池,开源一款轻量线程池项目,本节继续开源另一个孪生兄弟:内存池。 本节的线程池与内存池代码解析会在我的星球详细讲解。 内存池:https://github.com/Light-City/light-memory-pool 线程池:https://gi…

Python案例分析|使用Python图像处理库Pillow处理图像文件

本案例通过使用Python图像处理库Pillow,帮助大家进一步了解Python的基本概念:模块、对象、方法和函数的使用 使用Python语言解决实际问题时,往往需要使用由第三方开发的开源Python软件库。 本案例使用图像处理库Pillow中的模块、对象来处理…