设计模式(三)-结构型模式(6)-享元模式

一、为何需要享元模式(Flyweight)?

假如在网页中渲染这样的一个画面:大小不一的星星铺满了整个画布,并且都在不断的进行移动闪烁着。一批星星消失了,另一批又从另一边缘处出现。

请添加图片描述

要实现这样的渲染效果,在程序中就得需要创建这些星星,然后将它们一个个画上去。

有个问题就是,如果我们还得按照平常创建对象的方式,对每一颗出现的星星都创建一遍。一旦星星从画布上消失,就销毁并释放内存。这么做的话,系统就得要开销大量的内存空间,而且频繁进行创建销毁的操作会影响程序运行的性能。到最后我们只能看到这样的效果:一打开网页后,画布上的那些星星隔了一段时间才全部出现,滚动网页也不流畅。

为了解决“隔了一段时间才全部出现”和“滚动网页也不流畅”的问题(因为存储大量对象而导致开销大量内存、频繁创建销毁对象而导致程序的性能下降),我们就应该尽可能少的生成那些存在相同状态的星星。

其实在繁星点点的夜空中,总会有很多相似或者相同的星星存在着。因此我们可以只创建一颗星星作为一个共享对象,来代表这些与之相似的同类星星。在渲染的时候,只对这个共享对象重复的画上去就行了。

享元模式的定义:运用共享技术有效的支持大量细粒度的对象。

从以上的定义,我们先分析“享元”和”细粒度“这两个关键词。

  • 享元即共享元对象。元这个词,就好比数据库表中的一组若干个元数据,元数据是数据的最小单位。所以元对象的意思也就是,一组同类对象中的每一个元对象。既然这些同类对象都是相同状态的对象,我们只需要创建一个共享元对象来代表它们。
  • 细粒度如颗粒大小一样,即这个对象并不庞大而复杂。所以享元对象就应当是结构简单的对象。

但是我们怎么区分对象之间是否存在相同状态,然后将他们归类为某一组同类对象呢。于是可以对这些对象内的某个属性作为唯一标识,来判断是否为同类对象。比如星星,唯一标识可以是星星的半径值,也可以是速度值等。在这里我们选择的是以半径作为唯一标识。如图所示:
(在享元对象容器,每一个享元对象之间的半径值不相同,都代表着各自一组与自己半径相同的星星。)

请添加图片描述

享元对象是有了,但是我们编程中,起码也要保证唯一标识的半径值,是不能因为程序的变化而变化吧,即对象的外部不能对半径进行修改。这时候就有了外部状态和内部状态的区别。

外部状态: 存在于享元对象的外部。因环境变化而变化。(环境变化即客户端发生的状态变化)
内部状态: 存在于享元对象的内部。不能因环境变化而变化。

  • 外部状态是客户端进行的活动变化,比如为星星的位置进行随机布局,控制星星消失和出现的个数等等。

  • 内部状态是对象固有的状态,即一颗半径大小为4的星星,半径大小是固有的,客户端把它画上去时,不能强行使它变大变小。要保持内部状态,对象内的所有属性和状态都应当被保护起来,如对象内的所有字段都被设置为私有的访问机制。

特点:

  • 减少创建对象的数量,使用享元对象来代表一组同类对象。

结构:
抽象享元(Flyweight):具体享元类的基类。规定了具体享元要实现的方法,,也可以接受并作用于外部状态。(接受并作用于:传外部状态的参数到该方法内,以处理外部状态,但不改变享元内部状态。)
具体享元类(ConcreteFlyweight):实现抽象享元的方法。如果存在内部状态(即在享元容器里没有存在该享元时,就实例化一个),则增加存储空间。
享元工厂类(FlyweightFactory):创建和管理享元对象。
客户端类(Client):所有享元对象的引用,并存储对应的外部状态。(引用:把星星画上去;存储外部状态:存储星星的位置和出现的个数等)

适合应用场景特点:

  • 需要大量细粒度的对象,来完成某个功能。
  • 大量对象有存在着相同的状态。
  • 实际例子:线程池中的共享线程,解决频繁创建销毁线程的问题,字符串常量池中的共享字符串,解决重复创建存在值相同的字符串的问题…

请添加图片描述

二、例子

需求:

在视频剪辑中,用户想要在画布上实现满天星的效果,就做了如下参数的设置:
1)星星的总数为1000000,并且这些星星的大小和个数都是随机的;
2)星星的半径大小在 1~10 范围;
3)勾选默认星星的速度、闪烁频率、亮度都跟半径的大小有关,所以不用用户来自定义这些参数的值。

设计分析:

  • 以半径作为唯一标识,一个享元对象对应一组半径相同的对象。
  • 享元对象最多有 10 个。需要渲染的对象有1000000 个。

1、定义抽象享元和具体享元类


    //Flyweight:抽象享元(星星抽象)
    public interface IStar
    {
        void draw();
    }

    //ConcreteFlyweight:具体享元类(星星)
    public class Star:IStar
    {
        private int Radius;
        private double Brightness;
        private double Twinkle;
        private double Speed;

        public Star(int radius)
        {
            Radius = radius;
            //Brightness = ...;
            //Twinkle = ...;
            //Speed = ...; 
        }

        //1、args:外部状态可作为参数传入,但不能改变 Star 的内部状态
        public void draw(/*args:若有外部实例传入*/)
        {
            //处理一些与外部有关的逻辑...
            //外部参数不能改变 Star 类的所有属性值和其他内部状态
        }

        //2、如果该方法在客户端进行调用,而不是在享元工厂创建 Star 类的享元对象时调用:
        //那么该方法的内部逻辑是错误的,是因为不能通过外部 val 改变 Brightness 值。
        public void setBrightness(double val)
        {
            Brightness = val;
        }
    }


2、定义享元工厂类


    //FlyweightFactory:享元工厂类(创建和管理享元对象)
    public class FlyweightFactory
    {
        //享元对象的容器
        private Dictionary<int, IStar> StarsDict = new Dictionary<int, IStar>();
        //创建和获取享元对象,radius 为标识享元对象的参数。
        public IStar getStar(int radius)
        {
            IStar star = null;
            //在容器中是否存在跟 radius 值相同的对象
            StarsDict.TryGetValue(radius,out star);

            if(star == null)//若不存在,则创建一个享元并存入到容器里
            {
                star = new Star(radius);
                StarsDict.Add(radius,star);
            }

            return star;
        }
    }

3、主程序


//主程序
    class Program
    {
        static void Main(string[] args)
        {
            Random random = new Random(10);

            //享元模式-----------------
            FlyweightFactory factory = new FlyweightFactory();
            for (int i = 0; i < 1000000; i++)
            {
                //随机生成半径大小为1~10范围的星星。
                var star = factory.getStar(random.Next(1, 10));
                star.draw();//对外部状态的影响
            }
            //-------------------------

            //非享元模式----------------
            List<IStar> starList = new List<IStar>();
            for (int i = 0; i < 1000000; i++)
            {
                //创建了 1000000 个对象,执行整个循环的时间久。
                var star = new Star(random.Next(1, 10));
                starList.Add(star);
                star.draw();
            }
            //--------------------------

            //验证非享元模式和享元模式,在当前程序里使用内存大小的情况:
            var process = Process.GetCurrentProcess();
            var memorySize = process.PrivateMemorySize64 / (1024 * 1024);//单位为 M.
            Console.WriteLine(memorySize);

            //某次运行验证的结果:
            //享元模式,内存占用:17M;非享元模式,内存占用:60M;
            
            Console.ReadLine();
        }
    }

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

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

相关文章

C语言之初识C语言

文章目录 前言一、什么是C语言二、第一个C语言程序三、数据类型四、变量&#xff0c;常量1、变量1.1 变量的命名1.2 变量的分类1.3 变量的使用1.4 变量的作用域和生命周期2、变量 五、字符串1. 概念2. 求解字符串的长度【strlen】3. 转义字符【含笔试题】 六、注释七、选择语句…

ESP8266 TCP/串口透传

简介 先在PC上做测试, 使用串口软件对ESP8266 模块进行设置, 使用网络助手软件与串口软件进行自由收发设置 ATRST ## 复位 ATCWMODE_DEF1 ## 设置为Station模式 ATCWJAP_DEF“路由器wifi名称”,“路由器wifi密码” ## 设置ESP连接的路由器名称密码 ATCIPSTART“TCP”,“192.1…

Alpha突触核蛋白神经退行性疾病

Alpha突触核蛋白科研背景 ● Alpha突触核蛋白约 15kDa, 140个氨基酸 ● StressMarq在E. coli中过表达人源基因然后将蛋白从细胞质基质中纯化出来 ● 未折叠的alpha突触核蛋白单体在12% SDS-PAGE上为~15 kDa的条带 StressMarq/欣博盛生物的Alpha突触核蛋白有以下两类&#xf…

[uni-app] mescroll与 page 本身的滚动冲突处理, 动态禁用下拉刷新

参考贴: uniapp动态禁用mescroll-body组件的下拉刷新,或者动态禁用mescroll-body组件的上拉加载 记录问题场景 如图: 搜索和 第二个标签栏, 都是随页面滚动的, 当页面滚动一定距离, 会触发标签栏的吸顶 即如上图, 问题描述 当列表页面数据部满屏时, 且页面已经由于滚动而吸顶…

许可式邮件营销与垃圾邮件的区别:合规与效果的关键区分

接触过邮件营销的人一定不陌生“垃圾邮件”和“许可式邮件营销”这两个名词。在各大电商节到来之际&#xff0c;小编帮助大家弄清楚什么是垃圾邮件&#xff1f;什么是许可式邮件营销&#xff1f;为什么会变成垃圾邮件&#xff1f;怎么做许可式邮件营销&#xff1f;让大家在促销…

极智嘉(Geek+)货到人方案优势显著,助力拆零场景效率提升

众所周知&#xff0c;零售行业所面临的物流挑战比其他行业更为严峻。这是由于零售行业复杂的行业业态、繁多的商品种类、“唯快主义”的配送需求&#xff0c;以及零售行业的终端客户多为个体消费者&#xff0c;购买习惯以单件、多品为主&#xff0c;购买习惯具有周期性和爆发性…

minio 分布式对象存储

分布式文件系统应用 1.1、Minlo 介绍 Minlo 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象…

LVM将多个磁盘组成一个逻辑卷实战

LVM&#xff08;Logical Volume Manager&#xff09;是一种逻辑卷管理器&#xff0c;是Linux系统中的一个重要的存储管理技术。它的主要作用是将若干个硬盘分区或者物理硬盘合并成一个逻辑卷组&#xff08;Volume Group&#xff0c;简称VG&#xff09;&#xff0c;然后再将逻辑…

使用Flask逐步搭建Web应用程序

大家好&#xff0c;Flask是一个使用Python编写的轻量级Web应用框架。它被设计成简单、易于学习和使用的&#xff0c;同时具备足够的灵活性和扩展性&#xff0c;以满足各种规模的Web应用开发需求。本文我们将介绍一个使用Flask逐步搭建Web应用程序的简单入门示例。 1.安装Flask…

开发模型和测试模型

1. 开发模型 1.1 瀑布模型 瀑布模型是其他模型的基础框架 start—>需求分析---->计划----->设计----->编码----->测试----->End&#xff08;其实就是软件开发的生命周期&#xff09; 特点&#xff1a;线性的开发流程 缺陷&#xff1a;测试被后置。①风险往…

企业“数据入表”之政策及业务模式解读

2023年8月21日&#xff0c;财政部重磅发布了《企业数据资源相关会计处理暂行规定》&#xff08;以下简称“暂行规定”&#xff09;&#xff0c;该规定将于2024年1月1日正式施行。 “暂行规定”发布后&#xff0c;引起全社会的广泛关注&#xff0c;关注的焦点集中在数据入表概念…

指标体系构建-01-什么是数据指标

参考 四千字全面解析数据产品经理必知概念&#xff1a;标签、维度、指标 什么是数据指标 指标是指于其中打算达到的指数&#xff0c;规格&#xff0c;标准等,是用数据对事物进行描述的工具。通常指标对应是否有价值取决于这个指标的实际意义。同时关注指标对应的数值&#x…

Leetcode 134 加油站

题意理解&#xff1a; 给定n个站点&#xff0c;两个数组gas表达每个站点可加的油量&#xff0c;cost表达到下一站点所需耗费的油量。 gas [1,2,3,4,5], cost [3,4,5,1,2] 要求从下表为i的站点开始&#xff0c;刚好能支撑汽车在每个站点转一圈后回到出发位置。 解题思路&#…

天锐绿盾加密系统是做什么用的?——防止公司内部核心文件数据 \ 资料外泄

天锐绿盾加密系统是一款专业的数据加密软件&#xff0c;主要用于保护企业数据的安全性和完整性。该系统采用先进的加密技术&#xff0c;对数据进行加密处理&#xff0c;确保数据在传输和存储过程中的安全性。 PC访问地址&#xff1a;www.drhchina.com 天锐绿盾加密系统的主要…

RocketMQ系统性学习-RocketMQ高级特性之消息存储在Broker的文件布局

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 【11来了】文章导读地址&#xff1a;点击查看文章导读&#xff01; &#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f3…

【C语言】自定义类型:结构体深入解析(二)结构体内存对齐宏offsetof计算偏移量结构体传参

文章目录 &#x1f4dd;前言&#x1f320; 结构体内存对齐&#x1f309;内存对齐包含结构体的计算&#x1f320;宏offsetof计算偏移量&#x1f309;为什么存在内存对⻬?&#x1f320; 结构体传参&#x1f6a9;总结 &#x1f4dd;前言 本小节&#xff0c;我们学习结构的内存对…

Redis设计与实现之RDB

目录 一、RDB 1、保存 2、 SAVE 、BGSAVE 、AOF 写入和 BGREWRITEAOF SAVE BGSAVE 3、载入 4、RDB 文件结构 REDIS DB-DATA SELECT-DB KEY-VALUE-PAIRS EOF CHECK-SUM 二、 小结 一、RDB 在运行情况下&#xff0c;Redis 以数据结构的形式将数据维持在内存中&…

头歌—衍生密码体制

# 第1关&#xff1a;Rabin密码体制 题目描述 任务描述 Rabin密码体制是RSA密码体制的一种。 本关任务&#xff1a;使用Rabin密码体制对给定的明文进行加密。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;Rabin密码体制。 Rabin密码体制 在本关中&#x…

幻彩LED灯带芯片:SM16703SP单点单控 断点续传

幻彩LED灯带芯片SM16703SP3是一款单点单控断点续传的芯片&#xff0c;它采用了先进的技术&#xff0c;可以实现灯光的变化和控制。这款芯片不仅仅可以提供各种丰富多彩的灯光效果&#xff0c;还有断点续传功能&#xff0c; LED断点续传灯条采用了双信号线交叉传输的方案&#x…

【Spring Boot】面试题汇总,带答案的那种

继上次的文章【MySQL连环炮&#xff0c;你抗的住嘛&#xff1f;】爆火之后&#xff0c;越来越多的小伙伴后台留言&#xff0c;要求阿Q总结下其他的“连环炮”知识点&#xff0c;想在金九银十的面试黄金期轻松对线面试官。 同样为了节省大家的时间&#xff0c;阿Q最近对【Sprin…