重修设计模式-结构型-享元模式

news2024/9/24 17:06:04

重修设计模式-结构型-享元模式

复用不可变对象,节省内存

享元模式(Flyweight Pattern)核心思想是通过共享对象方式,达到节省内存和提高性能的目的。享元对象需是不可变对象,因为它会被多处代码共享使用,要避免一处代码对享元进行了修改,影响到其他使用它的代码。

享元模式的实现非常简单,主要是通过工厂模式,在工厂类中,通过一个 Map 或者 List 来缓存已经创建过的享元对象,来达到复用的目的。

举个例子,在线象棋游戏:

象棋可同时容纳上万个房间同时进行游戏,每个房间最基础的是象棋和棋盘。一副象棋有32个棋子,如果后每个房间都创建相同的棋子对象,就是千万级别的,对任何系统都是个挑战。

分析这个例子,棋子的字和颜色是固定的几个,跟场景无关;场景相关的只有只有棋子的坐标。那其实可以把棋子不变的部分抽取出来,设计为享元对象,让所有棋子共享,从而让每个棋子对象变得更轻量。

未使用享元模式的棋子对象:

//棋子
data class ChessPieceOld(
    val id: Long,
    val text: String,
    val color: ChessPieceUnit.Color,
    val positionX: Int,
    val positionY: Int
) {
}

当对象大量创建时,几个固定的属性(id、text、color)会大量重复,冗余占用内存。这时可以将这几个属性抽出,设计为享元类,并使用带缓存的工厂来生成享元对象:

//棋子固定信息-享元类
data class ChessPieceUnit(val id: Long, val text: String, val color: Color) {
    enum class Color {
        RED, BLACK
    }
}

//生成棋子-带缓存的创建工厂
class ChessPieceFactory {
    companion object {
        //享元对象池
        private val chessPiece = hashMapOf(
            1 to ChessPieceUnit(1, "車", ChessPieceUnit.Color.RED),
            2 to ChessPieceUnit(2, "馬", ChessPieceUnit.Color.BLACK),
            //...
        )

        fun getChessPiece(id: Int): ChessPieceUnit {
            return chessPiece[id]!!
        }
    }
}

//棋子
data class ChessPiece(val piece: ChessPieceUnit, val positionX: Int, val positionY: Int) {
}

//棋盘
class ChessBoard() {
    private val chessPieces: HashMap<Int, ChessPiece> = hashMapOf()

    fun init() {
        chessPieces.put(1, ChessPiece(ChessPieceFactory.getChessPiece(1), 0, 1))
        chessPieces.put(2, ChessPiece(ChessPieceFactory.getChessPiece(1), 0, 1))
        //...
    }

    fun move(chessPieceId: Int, positionX: Int, positionY: Int) {
        //...
    }
}

使用享元后,所有棋子的固定部分共同引用数量有限的享元对象,每个棋子对象变得更为轻量,内存中存储的冗余信息大大减少,避免了大量相似对象的开销,提高了系统资源的利用率。

注意上面的例子,使用享元模式后,棋子对象的数量并没有变化,只是对象比之前小了很多。消耗内存最多的成员变量已经被移动到很少的几个享元对象中了,这几个享元对象会被上千个情境小对象复用,无需再重复存储相同数据。

享元模式在 Java 语言中的应用

1.Java 中的字符串常量池

String 类会利用享元模式来复用相同的字符串常量,当某个字符串常量第一次被用到的时候,存储到常量池中,之后再用到的时候,直接引用常量池中已经存在的即可,不需要重复创建对象,通过下面代码可以验证。

//字符串常量池
String s1 = "秋意浓";
String s2 = "秋意浓";
String s3 = new String("秋意浓");//直接new出对象,绕过了Java的常量池优化
System.out.println(s1 == s2);   //输出:true
System.out.println(s1 == s3);   //输出:false

2.基本类型包装类

基本类型的包装类(如Integer 、Long、Short、Byte 等),也都利用了享元模式来缓存 -128 到 127 之间的数据。因为对于大部分应用来说这个区间是最常用的数值,如果预先创建所有值的享元对象不仅会占用大量内存,也会让类加载时间过长。

以 Integer 类型为例:

//包装类型的享元
Integer n1 = 1;
Integer n2 = 1;
Integer n3 = 129;
Integer n4 = 129;
System.out.println(n1 == n2);   //输出:true
System.out.println(n3 == n4);   //输出:false

因为 129 超出了享元对象池区间,所以每次会返回新的对象,两个对象地址不同,输出 false。Integer 享元部分源码如下:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

可以通过 JDK 参数修改缓存池上限(下限不支持修改):

//方法一:
-Djava.lang.Integer.IntegerCache.high=255
//方法二:
-XX:AutoBoxCacheMax=255

在使用 Java 进行编码时,要尽量避免直接 new 出包装类型或者字符类型对象,如 String s = new String("aaa")Integer n = new Integer(1),这样会绕过系统的享元模式,创建重复对象。推荐直接使用自动装箱语法,如:Integer n = 1,或通过类型工厂的方式:Integer n = Integer.valueof(1)

上面两个场景都是享元模式在 Java 中的应用,只是实现略有不同:Integer 类中要共享的对象,是在类加载的时候就一次性创建好的;String 类的享元,是在某个字符串第一次被用到的时候,才创建并存储到常量池中的,相当于懒加载方式。

享元模式的类似设计

1.享元模式 vs 单例

虽然享元模式的实现和单例的变体多例非常相似,但它们的设计意图不同:享元模式是为了对象复用,节省内存。而多例是为了限制对象的个数。

2.享元模式 vs 对象池

虽然享元模式和对象池都是为了复用,但他们的“复用”也是不同的概念:

  • 对象池中的“复用”可以理解为“重复使用”,使用时被使用者独占,使用完成后放回池中,主要目的是节省时间。
  • 享元模式中的“复用”可以理解为“共享使用”,在整个生命周期中,都是被所有使用者共享的,主要目的是节省空间。

总结

当一个系统中存在大量重复对象的时候,我们就可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,这样可以减少内存中对象的数量,以起到节省内存的目的。

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

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

相关文章

【网络安全】公钥密码体制

1. 公钥密码体制概述 1.1 基本概念 公钥密码体制&#xff0c;又称为非对称密码体制&#xff0c;是一种基于数学函数的加密方式&#xff0c;它使用一对公钥和私钥来进行加密和解密。公钥用于加密&#xff0c;私钥用于解密。这种体制提供了一种安全的通信方式&#xff0c;因此在…

安装程序不用鼠标,Windows也玩程序包管理存储库

网管小贾 / sysadm.cc “嘿&#xff0c;嘿&#xff0c;看见没&#xff0c;今年某某著名大学建筑专业才招了4名新生&#xff01;” 大刘用手点指手机&#xff0c;带着一脸的吃惊相。 我冲他笑了笑&#xff0c;说道&#xff1a;“那是他们的教学水平不行。” “要是换了我&…

【JS】正则表达

正则表达式 reg /匹配规则/ reg.test(str) 1.边界符&#xff1a;^ 以...开头&#xff0c;$ 以...结尾 2.量词&#xff1a;* 出现0次或多次&#xff0c; 出现1次或多次, ? 出现0次或1次,{n}出现n次&#xff0c;{n,m}出现n到m次 3.字符类&#xff1a;[]中的字符任一出现&…

无人机的避障的航迹规划详解!!!

一、无人机避障技术 视觉避障系统&#xff1a;通过安装在无人机上的摄像头捕捉周围环境的图像&#xff0c;利用计算机视觉技术对图像进行处理和分析&#xff0c;提取出障碍物的信息。这种方法直观、信息丰富&#xff0c;但在光线不足或变化多的情况下可能影响识别效果&#xf…

生成测试图片的步骤

生成测试图片的步骤&#xff1a; 1、通义万象画图&#xff1a;https://tongyi.aliyun.com/wanxiang/creation 2、改图宝修改尺寸&#xff1a;https://www.gaitubao.com/

set的使用

序列式容器和关联式容器 序列式容器&#xff1a; 前⾯我们已经接触过STL中的部分容器如&#xff1a;string、vector、list、deque、array、forward_list等&#xff0c;这些容器统称为序列式容器&#xff0c;因为逻辑结构为线性序列的数据结构&#xff0c;两个位置存储的值之间…

【Python报错已解决】AttributeError: ‘Tensor‘ object has no attribute ‘kernel_size‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

使用compile_commands激活vscode索引-跳转-代码提示功能

最近发现&#xff0c;使用vscode打开一个大的c工程很容易无法正常调转和代码提示。所以经常会手动修改.vscode/c_cpp_properties.json文件的"includePath"属性。然而&#xff0c;当pkg越来越多 工程体量越来越大之后&#xff0c;我不得不探索如何自动的完成这一过程&…

Matplotlib画图相关代码

绘制不同类型的线条 import matplotlib.pyplot as plt import numpy as npx np.array([1, 2, 3]) y np.array([2, 4, 6])# 不同线型的示例 plt.plot(y, marker*, linestyle-) # 实线 plt.plot(y 1, markero, linestyle--) # 虚线 plt.plot(y 2, markerx, linestyle-.)…

【IDEA配置Maven环境】

在IDEA欢迎界面 选择 IDEA中 Customize > ALLSettings > Build,Execution,Deployment > Build Tools > Maven

VirtualBox+Vagrant快速搭建Centos7系统【最新详细教程】

VirtualBoxVagrant快速搭建Centos7系统 &#x1f4d6;1.安装VirtualBox✅下载VirtualBox✅安装 &#x1f4d6;2.安装Vagrant✅下载Vagrant✅安装 &#x1f4d6;3.搭建Centos7系✅初始化Vagrantfile文件生成✅启动Vagrantfile文件✅解决 vagrant up下载太慢的问题✅配置网络ip地…

从零开始的软件开发详解:数字药店系统源码与医保购药APP

很多小伙伴们疑问&#xff0c;医保购药APP是如何开发的&#xff0c;今天我将从零数字药店系统源码开始为大家提供一条清晰的实现方案。 一、技术架构设计 在开发医保购药APP之前&#xff0c;首先需要明确技术架构。一般来说&#xff0c;APP的技术架构可以分为前端和后端。 1…

网络分段:您需要了解的一切

什么是网络分段&#xff1f;为什么它很重要&#xff1f; 在当今互联互通的世界中&#xff0c;网络分段已成为组织网络安全战略中不可或缺的一部分。随着网络威胁不断演变和变得更加复杂&#xff0c;保护网络免受潜在入侵并尽量减少攻击面变得至关重要。根据最近的研究&#xf…

C++ 进阶之路:非类型模板参数、模板特化与分离编译详解

目录 非类型模版参数 类型模板参数 非类型模板参数 非类型模板参数的使用 模板的特化 函数模板的特化 类模板的特化 全特化与偏特化 偏特化的其它情况 模板的分离编译 什么是分离编译 为什么要分离编译 为什么模板不能分离编译 普通的类和函数都是可以分离编译的…

数据定义语言CREATE的应用

新书速览|SQL Server 2022从入门到精通&#xff1a;视频教学超值版_sql server 2022 出版社-CSDN博客 《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 (jd.com) SQL Se…

相交链表 -------------应用

给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&…

2024上海工博会,正运动机器视觉运动控制一体机应用预览(三)

■展会名称&#xff1a; 第二十四届中国国际工业博览会&#xff08;以下简称“上海工博会”&#xff09; ■展会日期 2024年9月24日–28日 ■展馆地点 中国国家会展中心&#xff08;上海&#xff09; ■展位号 6.1H-E261 本次上海工博会&#xff0c;正运动技术将携高性…

微信小程序开发项目

微信小程序是一种轻量级的应用程序&#xff0c;无需下载即可使用&#xff0c;并且具有即用即走的特点。这种新型的应用程序正在改变人们的使用习惯&#xff0c;使得人们可以更加方便快捷地获取信息和服务。本文将详细介绍微信小程序的开发过程&#xff0c;包括开发前的准备、开…

9.3 Linux_文件I/O_相关函数

打开与关闭 1、打开文件 int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);返回值&#xff1a;成功返回文件描述符&#xff0c;失败返回EOF pathname&#xff1a;文件路径 flags&#xff1a;标志&#xff0c;其中O_RDO…

CUDA安装教程+显卡驱动安装

CUDA安装教程显卡驱动安装 新的ubuntu系统&#xff0c;需要重新安装显卡驱动&#xff0c;以及cuda&#xff0c;记录以下坑点&#xff1a; 先安装显卡 nvidia-smi输入后出现 首先知道自己的GPU型号&#xff0c;如RTX 4090 这里下载&#xff1a;https://www.nvidia.com/Down…