设计模式——1_5 享元(Flyweight)

今人不见古时月,今月曾经照古人

——李白

文章目录

  • 定义
  • 图纸
  • 一个例子:可以复用的样式表
    • 绘制表格
    • 降本增效?
      • 第一步,先分析 变化和不变的地方
      • 第二步,把变化和不变的地方拆开来
      • 第三步:有没有办法共享这些内容完全相同的样式对象?
  • 碎碎念
    • 抽象变化的部分 & 抽象不变的部分
    • 享元和单例
    • 享元和String.intern()
    • 享元和活字印刷

定义

运用共享技术有效地支持大量颗粒度对象

享元 真是一个非常非常优秀的翻译

如果你单看 四人组 对享元的定义,那很容易就会把他理解成类似 连接池 那种对对象进行共享的模式。但是如果享元就是对对象池的管理的话,那他的元字如何解释呢?窃以为,这里的元应该做 【元件】 讲,共享元件,才叫享元。享元不是对整个大对象进行对象池管理,而是对大对象中的重复部分抽离出来进行管理。


图纸

在这里插入图片描述


一个例子:可以复用的样式表

在 css 中,我们会把经常用到的样式组合成一个 css 类,然后在标签中通过 class=XX 的方式让N个标签引用同样的样式

如果你用过 jxl 或者 poi 这样的框架操作过 Excel 表格的话,就会发现你可以在一个 Sheet 里创建的样式是有上限的。可是 C# 里用于操作 office 的框架却没有这个烦恼。当然微软一家亲肯定有一些优化,可问题在于他怎么做到的呢?

准备好了吗?这次的例子开始了:


绘制表格

假如我们现在要 绘制一个表格的框架,就像这样:

在这里插入图片描述

//格
public class Cell {

    //设定字体
    private Font font;
    //背景颜色
    private Color background;
    //要展示的值
    private String value;

    public void draw(){
        System.out.println("根据font+background+value进行绘制");
    }
    
    …… getter & setter & other 略
}

//行
public class Row {

    private final List<Cell> cells;

    public Row(List<Cell> cells) {
        this.cells = cells;
    }

    public void draw(){
        for (Cell cell : cells) {
            cell.draw();
        }
    }
    
    ……
}

//表
public class Sheet {

    private final List<Row> rows;

    public Sheet() {
        rows = new ArrayList<>();
    }

    public void draw() {
        for (Row row : rows) {
            row.draw();
        }
    }
    
    ……
}

这个框架特别好,我就喜欢这种一眼能看到头不藏着掖着的框架。这玩意,他单纯

可是太单纯也有问题,某天我们用他处理一个 1000*10 的表格的时候他直接就崩溃了

经过分析我们发现在绘制表格的时候程序占用的内存异常高,10000个格子,意味着我们会有10000个cell对象 和 10000个Font对象 以及 10000个Color对象,有没有办法简化他?


降本增效?

重构代码的方式总是大同小异的

第一步,先分析 变化和不变的地方

我们发现,1000个Row对象和10000个Cell对象 肯定是节约不了的,而 Cell 中最关键的 Value 每一格都不相同,哪怕碰巧一致,不确定性太多,无法公用

那就只能从 样式 上打主意了,Cell 通过 一个 Font 对象 和 一个 Color 对象来管理 Cell 的样式,而一个表格里面字体不超过5种,颜色不超过10种。我们却创建了整整 10000 个 Font 对象和 10000 个 Color 对象。这显然是不合理的


第二步,把变化和不变的地方拆开来

这一步已经完成了, 因为 CellFontColor 本来就是通过组合绑定在一起的


第三步:有没有办法共享这些内容完全相同的样式对象?

当然有

我们可以创建 关于Font的对象池关于Color的对象池,然后创建对应的工厂方法,要求 client 必须通过工厂方法来获取 FontColor 对象。当我们发现 client 获取已经在池中存在的 FontColor 对象的时候,直接从池里面把 已有对象 返还给 client;如果 client 获取的是全新的样式,则新建对应的 FontColor 对象返还给 client,并把新建的对象写入池中

就像这样:

在这里插入图片描述

public class StyleFactory {

    //字体池
    private List<Font> fontPool = new LinkedList<>();
    //颜色池
    private List<Color> colorPool = new LinkedList<>();

    public Font getFont(int size, String fontFamily, Color color) {
        for (Font font : fontPool) {
            if (font.getSize() == size
                    && font.getFontFamily().equals(fontFamily)
                    && font.getColor().equals(color)/*Color的equals方法已经重构过了*/) {
                return font;
            }
        }

        Font result = new Font(size, fontFamily, color);
        fontPool.add(result);
        return result;
    }

    public Color getColor(int red, int green, int blue) {
        for (Color color : colorPool) {
            if (color.getRed() == red
                    && color.getGreen() == green
                    && color.getBlue() == blue) {
                return color;
            }
        }

        Color result = new Color(red, green, blue);
        colorPool.add(result);
        return result;
    }
}

我们增加了 fontPoolcolorPool 这两个列表用于管理程序中已经出现过的 FontColor对象

然后通过定义 StyleFactory 的方式管理 FontColor 对象创建的方式,同时管理池,最终实现相同样式的共享,从而大大降低了内存损耗

而这正是一个标准的 享元 实现


为啥要用遍历而不是映射表

因为没必要,上文已经讲过 Font 在一个表格中出现次数不超过 5 种,Color 不超过 10 种。这种数量级的遍历根本不会对性能产生影响



碎碎念

抽象变化的部分 & 抽象不变的部分

在之前的设计模式中,我们通常是以不变的部分作为基层,把变化的部分剥离开来,让他去和不变的部分进行组合以实现降低耦合的效果

但享元比较特殊,他是少有的剥离不变的部分,以变化的部分为基准,让变化的部分去找不变的部分的设计模式


享元和单例

如果把享元的内容从对象内的某些状态,拓展到整个对象,这时候的享元就是对整个对象的对象池管理,此时我们甚至可以说单例是极致的享元

但是享元和单例的本质是不同的

  • 单例讲究从根本上只有一个对象,而且这一个对象的状态也是共享的部分,所以他也应该是可变的
  • 享元讲究一种状态一个对象,一个享元对象被创建出来后,他的状态应该是不可变的。因为当你改变享元对象的状态时,所有引用这个对象的外部对象都会因此而改变,这并不是我们用享元想要看到的效果。

所以硬要在创建型模式里找一个跟享元像的话,倒不如说是原型的思想更像一点:

  • 对原型来说,创建一个新对象花销太大了,那行我不创建了,我复制
  • 对享元来说,大量对象都有 地址不同的相同状态 占用太大了,那行别创建了,大家都用同一个就完事了

享元和String.intern()

首先我们要知道这个intern方法是用来干嘛的:

String 中的 intern() 是一个本地方法,他的作用为:假如一个字符串在 常量池 中已经存在了,则返还常量池中的该字符串;如果不存在,则把该字符串放入常量池,再返还常量池中的该字符串对象的引用

有没有觉得他跟享元超级像,其实Java中的String就是用了享元的思想。我们知道String是不可变的,我们创建的字符串会被放到某个公共区域(常量池),以便程序中所有的类共享这些字符串信息。JVM一定要这样管控字符串,如果为所有的字符串都创建全新的信息,那一下子就爆内存了。

可别认为这只是一个可知可不知的冷知识,面试官是很喜欢问问他有关的问题的,比如他可以这样问:

请问以下代码会如何输出:

String str1 = new StringBuilder("a").append("b").toString();
System.out.println(str1.intern() == str1);

String str2 = new StringBuilder("ja").append("va").toString();
//String str2 = new StringBuilder("a").append("b").toString(); //这样写效果也是一样的
System.out.println(str2.intern() == str2);

答案是这样的:

  • 1.6及以下时输出 false、false
  • 1.7及以上时输出 true、false

至于原因,咱这是讲设计模式的,就不展开了(这玩意跟堆 栈 永久代有关系,太长了,有机会整理JVM的笔记时再唠吧


享元和活字印刷

就是古代四大发明里面哪个活字印刷

仔细想想,其实活字印刷就是享元思想的体现:

把每个字都制作成小方块放到字库中,需要的时候先到字库里面找,如果没找到,刻一个先用,用完再把刚刻好的那个方块放到字库中,方便下次调用

古人的智慧真是不可小觑




万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容

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

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

相关文章

C++(Qt)软件调试---静态分析工具clang-tidy(18)

C(Qt)软件调试—静态分析工具clang-tidy&#xff08;18&#xff09; 文章目录 C(Qt)软件调试---静态分析工具clang-tidy&#xff08;18&#xff09;1、概述2、clang-tidy基本用法3、目前已有检查项4、Qt Creator中安装clang-tidy5、Qt Creator中使用clang-tidy6、Clang-Tidy配置…

66.Go从零搭建一个orm框架【简版】

文章目录 一&#xff1a;前置学习1、 为什么要用orm2、Golang里面是如何原生连接MySQL的3、ORM框架构想 二: 开始造1、连接Connect2、设置/读取表名Table/GetTable3、新增/替换Insert/Replace4、条件Where5、条件OrWhere6、删除Delete7、修改Update8、查询9、设置查询字段Field…

Linux Shell alias的简单用法:给shell起别名

alias&#xff1a;显示该用户所有起过别名的命令 alias lla‘ls -al’&#xff1a;给ls -al起别名为lla unalias lla&#xff1a;取消lla的别名 1、该命令所有的操作只对个人用户生效&#xff0c;给普通用户起的别名在root用户下不生效&#xff0c;只有回到普通用户才生效。 2…

解决Git添加.gitignore文件后不生效的问题

1. 问题描述 如上图所示&#xff0c;在已存在.gitignore文件且已经提交过的Git管理的项目中&#xff0c;其中.class、.jar文件以及.idea目录内的内容全部都还是被Git管理了&#xff0c;可见.gitignore文件并没有生效。 2. 原因发现 .gitignore文件只能作用于 Untracked Files…

Kafka 问题排查

订单宽表数据不同步 事情的起因是专员在 ze app 上查不到订单了&#xff0c;而订单数据是从 mysql 的 order_search_info 查询的&#xff0c;order_search_info 表的数据是从 oracel 的 BZ_ORDER_INFO 表同步过来的&#xff0c;查不到说明同步有问题 首先重启&#xff0c;同步…

linux性能优化-磁盘I_O优化

1.文件系统 1.1.文件系统的工作原理 文件系统是在磁盘的基础上&#xff0c;提供了一个用来管理文件的树状结构。 接下来我们就看看Linux 文件系统的工作原理。 1.1.1索引节点和目录项 在 Linux 中一切皆文件 ,文件系统,本身是对存储设备上的文件&#xff0c;进行组织管理的…

提升认知,推荐15个面向开发者的中文播客

前言 对于科技从业者而言&#xff0c;无论是自学成才的程序员&#xff0c;还是行业资深人士&#xff0c;终身学习是很有必要的&#xff0c;尤其是在这样一个技术快速迭代更新的时代。 作为一个摆脱了时间和空间限制的资讯分享平台&#xff0c;播客&#xff08;Podcast&#x…

Parasoft C++Test安装指南_含独立版和插件版

文章目录 前言一、Windows环境下1. 安装独立版CTest2. 安装插件版CTest 二、Linux环境下1. 安装独立版CTest 总结 前言 CTest是Parasoft公司出品的一款可以针对C/C源代码进行静态分析、单元测试、集成测试的测试工具&#xff0c;在C/C白盒测试领域被广泛使用。本篇文章主要讲解…

黑马苍穹外卖学习Day10

文章目录 Spring Task介绍cron表达式入门案例 订单状态定时处理需求分析代码开发功能测试 WebSocket介绍入门案例 来单提醒需求分析代码开发 客户催单需求分析代码开发 Spring Task 介绍 cron表达式 入门案例 订单状态定时处理 需求分析 代码开发 新建一个task包里面编写代码…

140基于matlab的求解转子系统前三个临界转速和主振型的传递矩阵法转子系统的不平衡响应

基于matlab的求解转子系统前三个临界转速和主振型的传递矩阵法转子系统的不平衡响应。参数涉及等截面参数、材料参数、轮盘参数。程序已调通&#xff0c;可直接运行。 140 matlab临界转速及振型 (xiaohongshu.com)

Vue开发者工具是什么?有哪些?

Vue开发者工具是什么&#xff1f; Vue开发者工具是用于Vue.js开发的浏览器扩展&#xff0c;提供了许多有用的功能和工具&#xff0c;帮助开发者更轻松地开发和调试Vue.js应用程序。 Vue开发者工具的功能特点有哪些&#xff1f; 1、组件树视图&#xff1a;Vue开发者工具可以显…

【C++初阶】第二站:类与对象(上) -- 下部分

前言&#xff1a; 本章知识点&#xff1a; 类对象模型、 this 指针 专栏&#xff1a; C初阶 目录 类对象模型 如何计算类对象的大小 类对象的存储方式猜测 结构体内存对齐规则 this指针 this指针的引出 this指针的特性 C语言和C实现Stack的对比 C语言实现 C实现 类对象模型 …

JavaEE:多线程(2):线程状态,线程安全

目录 线程状态 线程安全 线程不安全 加锁 互斥性 可重入 死锁 死锁的解决方法 Java标准库中线程安全类 内存可见性引起的线程安全问题 等待和通知机制 线程饿死 wait notify 线程状态 就绪&#xff1a;线程随时可以去CPU上执行&#xff0c;也包含在CPU上执行的…

SpringBoot 自定义Banner

SpringBoot自定义Banner SpringBoot项目启动的时候会输出一个Spring的图标。 思考&#x1f914; 它是在哪个环节的时候输出的呢&#xff1f;是否可以自定义图标呢&#xff1f; 源码分析 通常我们SpringBoot项目是通过SpringBoot.run(Appcation.class)启动的。 run() public Co…

【算法竞赛C++STL基础】栈,链表,队列,优先队列,map,set以及迭代器的用法

文章目录 1&#xff0c;前知——模板函数的实现2, hash 表1&#xff0c;定义2,ASCII码表3&#xff0c;咉射关系 3&#xff0c;迭代器4&#xff0c;STL关系1,stl 的基础关系2,stl 的分类1,相关分类2,相关简介顺序容器关联容器适配容器 3. STL 的相关函数的学习3.1 STL函数中都含…

buctoj——2024寒假集训 进阶训练赛 (五)

问题 A: 约瑟夫问题 题目描述 N个人围成一圈&#xff0c;从第一个人开始报数&#xff0c;数到M的人出圈&#xff1b;再由下一个人开始报数&#xff0c;数到M的人出圈&#xff1b;……输出依次出圈的人的编号。N&#xff0c;M由键盘输入。 输入 一行&#xff0c;包含两个正整数…

Linux 入门命令大全汇总 + Linux 集锦大全 【20240122】

文章目录 Linux 入门命令大全汇总Linux 集锦大全更多信息 Linux 入门命令大全汇总 别有一番风趣的alias 刚刚好合适的 apropos 命令 迷你计算器 bc 可看黄道吉日的 cal 全文可查看&#xff1a; Linux入门命令大全全文 Linux 集锦大全 linux终端中最漂亮的几款字体介绍及…

golang学习笔记——http.Handle和http.HandleFunc的区别与type func巧妙运用

文章目录 http.Handle和http.HandleFunc的区别http.Handle分析type func巧妙运用 http.HandleFunc分析总结参考资料 http.Handle和http.HandleFunc的区别 http.Handle和http.HandleFunc的区别体现了Go语言接口的巧妙运用 下面代码启动了一个 http 服务器&#xff0c;监听 808…

深入理解C语言(2):字符、字符串与内存函数

文章主题&#xff1a;字符、字符串与内存函数&#x1f30f;所属专栏&#xff1a;深入理解C语言&#x1f4d4;作者简介&#xff1a;更新有关深入理解C语言知识的博主一枚&#xff0c;记录分享自己对C语言的深入解读。&#x1f606;个人主页&#xff1a;[₽]的个人主页&#x1f3…

jdk17新特性——Switch表达式增强

目录 一、Switch表达式增强示例一1.1、传统的方式 case中变量赋值示例1.2、jdk17 case中变量赋值示例 二、Switch表达式增强示例二2.1、传统的方式 case中值匹配多个示例2.2、jdk17 case中值匹配多个示例 三、Switch表达式增强示例三3.1、传统的方式 case中需要多行业务代码示例…