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

2024-01-19 04:44

本文主要是介绍设计模式——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的笔记时再唠吧


享元和活字印刷

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

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

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

古人的智慧真是不可小觑




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

这篇关于设计模式——1_5 享元(Flyweight)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/621298

相关文章

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

设计模式之工厂模式(通俗易懂--代码辅助理解【Java版】)

文章目录 1、工厂模式概述1)特点:2)主要角色:3)工作流程:4)优点5)缺点6)适用场景 2、简单工厂模式(静态工厂模式)1) 在简单工厂模式中,有三个主要角色:2) 简单工厂模式的优点包括:3) 简单工厂模式也有一些限制和考虑因素:4) 简单工厂模式适用场景:5) 简单工厂UML类图:6) 代码示例: 3、工厂方法模式1) 在工厂方法模式中,有4个主要角色:2) 工厂方法模式的工作流程

C#设计模式(1)——单例模式(讲解非常清楚)

一、引言 最近在学设计模式的一些内容,主要的参考书籍是《Head First 设计模式》,同时在学习过程中也查看了很多博客园中关于设计模式的一些文章的,在这里记录下我的一些学习笔记,一是为了帮助我更深入地理解设计模式,二同时可以给一些初学设计模式的朋友一些参考。首先我介绍的是设计模式中比较简单的一个模式——单例模式(因为这里只牵涉到一个类) 二、单例模式的介绍 说到单例模式,大家第一

漫谈设计模式 [12]:模板方法模式

引导性开场 菜鸟:老大,我最近在做一个项目,遇到了点麻烦。我们有很多相似的操作流程,但每个流程的细节又有些不同。我写了很多重复的代码,感觉很乱。你有啥好办法吗? 老鸟:嗯,听起来你遇到了典型的代码复用和维护问题。你有没有听说过“模板方法模式”? 菜鸟:模板方法模式?没听过。这是什么? 老鸟:简单来说,模板方法模式让你在一个方法中定义一个算法的骨架,而将一些步骤的实现延迟到子类中。这样,你可

漫谈设计模式 [9]:外观模式

引导性开场 菜鸟:老鸟,我最近在做一个项目,感觉代码越来越复杂,我都快看不懂了。尤其是有好几个子系统,它们之间的调用关系让我头疼。 老鸟:复杂的代码确实让人头疼。你有没有考虑过使用设计模式来简化你的代码结构? 菜鸟:设计模式?我听说过一些,但不太了解。你觉得我应该用哪个模式呢? 老鸟:听起来你的问题可能适合用**外观模式(Facade Pattern)**来解决。我们可以一起探讨一下。

设计模式大全和详解,含Python代码例子

若有不理解,可以问一下这几个免费的AI网站 https://ai-to.cn/chathttp://m6z.cn/6arKdNhttp://m6z.cn/6b1quhhttp://m6z.cn/6wVAQGhttp://m6z.cn/63vlPw 下面是设计模式的简要介绍和 Python 代码示例,涵盖主要的创建型、结构型和行为型模式。 一、创建型模式 1. 单例模式 (Singleton

漫谈设计模式 [6]:适配器模式

引导性开场 菜鸟:老鸟,我最近在项目中遇到一个问题,我们的系统需要集成一个新的第三方库,但这个库的接口和我们现有的代码完全不兼容。我该怎么办? 老鸟:这是个常见的问题,很多开发者都会遇到这种情况。你有没有听说过适配器模式? 菜鸟:适配器模式?没有,能详细说说吗? 老鸟:当然可以!这就是我们今天要讨论的主题。适配器模式是一个设计模式,可以帮助我们解决你现在遇到的问题。 渐进式介绍概念 老

2 观察者模式(设计模式笔记)

2 观察者模式(别名:发布-订阅) 概念 定义对象间的一种一对多的依赖关系,当一个对象状态发生变化时,所以依赖于它的对象都得到通知并被自动更新。 模式的结构与使用 角色 主题(Subject)观察者(Observer)具体主题(ConcreteSubject)具体观察者(ConcreteObserver) 结构 Subject依赖于Observer最重要!!! package

1 单例模式(设计模式笔记)

1 单例模式 概述:使得一个类的对象成为系统中的唯一实例。 具体实现: 构造函数私有化 限制实例的个数 懒汉式(时间换空间) public class Singleton2 {public static Singleton2 singleton2;private Singleton2(){}public static Singleton2 getInstance() throws I

第三章 UML类图简介(设计模式笔记)

第三章 UML类图简介 3.1类 3.2接口 名字层必须有<> 3.3 泛化(继承)关系 箭头终点端指向父类(空心三角形) 3.4 关联(组合1)关系 B类是A类的成员变量 ,称A关联B。 箭头终点端指向B 3.5 依赖(组合2)关系 B类是A类的某个方法的参数 ,称A依赖B。 箭头终点端指向B(虚线) 3.6 实现关系 箭头终点端指向接口(虚线,空心