Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475
享元(Flyweight)模式:顾名思义就是被共享的单元。意图是复用对象,节省内存,提升系统的访问效率。比如在红白机冒险岛游戏中的背景花、草、树木等对象,实际上是可以多次被不同场景所复用共享,也是为什么以前的游戏占用那么小的内存,却让我们感觉地图很大的原因。

一、享元模式介绍

1.1 享元模式的定义

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

这里就提到了两个要求:细粒度和共享对象。而正是因为要求细粒度,那么势必会造成对象数量过多而且对象性质相近。所以我们可以将对象分为:内部状态和外部状态,内部状态指对象共享出来的信息,存储在享元信息内部,不会随着环境改变;外部状态指对象得以依赖的标记,会随着环境改变,不可以共享。根据是否共享,可以分成两种模式:

  • 单纯享元模式:该模式中所有具体享元类都是可以共享,不存在非共享具体享元类
  • 复合享元模式:将单纯享元对象使用组合模式加以组合,可以形成复合享元对象

实际上享元模式的本质就是缓存共享对象,降低内存消耗

1.2 享元模式的结构

我们可以根据享元模式的定义画出大概的结构图,如下所示:

  • FlyweightFactory:享元工厂,负责创建和管理享元角色
  • Flyweight:抽象享元,是具体享元类的基类,提供具体享元需要的公共接口
  • SharedFlyweight、UnSharedFlyweight:具体享元角色和具体非享元类
  • Client:客户端,调用具体享元和非享元类

1.3 享元模式的实现

根据上面的类图可以实现如下代码:

/*** @description: 抽象享元类* @author: wjw* @date: 2022/4/2*/
public interface Flyweight {/*** 抽象享元方法* @param state 代码外部状态值*/public void operation(int state);
}
/*** @description: 具体享元类* @author: wjw* @date: 2022/4/2*/
public class SharedFlyweight implements Flyweight{private String key;public SharedFlyweight(String key) {System.out.println("具体的享元类:" + key + "已被创建");}@Overridepublic void operation(int state) {System.out.println("具体的享元类被调用:" + state);}
}
/*** @description: 非共享的具体类,并不强制共享* @author: wjw* @date: 2022/4/2*/
public class UnSharedFlyweight implements Flyweight{public UnSharedFlyweight() {System.out.println("非享元类已创建");}@Overridepublic void operation(int state) {System.out.println("我是非享元类" + state);}
}
/*** @description: 享元工厂类,负责创建和管理享元类* @author: wjw* @date: 2022/4/2*/
public class FlyweightFactory {private HashMap flyweights = new HashMap<>();public FlyweightFactory() {flyweights.put("flyweight1", new SharedFlyweight("flyweight1"));}public Flyweight getFlyweight(String key) {return flyweights.get(key);}
}
/*** @description: 客户端类* @author: wjw* @date: 2022/4/2*/
public class Client {public static void main(String[] args) {FlyweightFactory flyweightFactory = new FlyweightFactory();Flyweight flyweight1 = flyweightFactory.getFlyweight("flyweight1");flyweight1.operation(1);UnSharedFlyweight unSharedFlyweight = new UnSharedFlyweight();unSharedFlyweight.operation(2);}
}

测试结果:

具体的享元类:flyweight1已被创建
具体的享元类被调用:1
非享元类已创建
我是非享元类2

二、享元模式应用场景

2.1 在文本编辑器中的应用

如果按照每一个字符设置成一个对象,那么对于几十万的文字,存储几十万的对象显然是不可取,内存的利用率也不够高,这个时候可以将字符设置成一个共享对象,它同时可以在多个场景中使用。不同的场景用字体font、字符大小size和字符颜色colorRGB来进行区分。具体实现如下:

/*** @description: 字的格式,享元类* @author: wjw* @date: 2022/4/2*/
public class CharacterStyle {private String font;private int size;private int colorRGB;public CharacterStyle(String font, int size, int colorRGB) {this.font = font;this.size = size;this.colorRGB = colorRGB;}@Overridepublic boolean equals(Object obj) {CharacterStyle otherCharacterStyle = (CharacterStyle) obj;return font.equals(otherCharacterStyle.font)&& size == otherCharacterStyle.size&& colorRGB == otherCharacterStyle.colorRGB;}
}
/*** @description: 字风格工厂类,创建具体的字* @author: wjw* @date: 2022/4/2*/
public class CharacterStyleFactory {private static final List styles = new ArrayList<>();public static CharacterStyle getStyle(String font, int size, int colorRGB) {CharacterStyle characterStyle = new CharacterStyle(font, size, colorRGB);for (CharacterStyle style : styles) {if (style.equals(characterStyle)) {return style;}}styles.add(characterStyle);return characterStyle;}
}
/*** @description: 字类* @author: wjw* @date: 2022/4/2*/
public class Character {private char c;private CharacterStyle style;public Character(char c, CharacterStyle style) {this.c = c;this.style = style;}@Overridepublic String toString() {return style.toString() + c;}
}
/*** @description: 编辑输入字* @author: wjw* @date: 2022/4/2*/
public class Editor {private List chars = new ArrayList<>();public void appendCharacter(char c, String font, int size, int colorRGB) {Character character = new Character(c, CharacterStyleFactory.getStyle(font, size, colorRGB));System.out.println(character);chars.add(character);}
}
/*** @description: 客户端测试类* @author: wjw* @date: 2022/4/2*/
public class EditorTest {public static void main(String[] args) {Editor editor = new Editor();System.out.println("相同的字--------------------------------------");editor.appendCharacter('t', "宋体", 12, 7777);editor.appendCharacter('t', "宋体", 12, 7777);System.out.println("不相同的字------------------------------------");editor.appendCharacter('t', "宋体", 12, 7777);editor.appendCharacter('x', "宋体", 12, 7777);}
}

测试结果如下:

相同的字--------------------------------------
cn.ethan.design.flyweight.CharacterStyle@610455d6t
cn.ethan.design.flyweight.CharacterStyle@610455d6t
不相同的字------------------------------------
cn.ethan.design.flyweight.CharacterStyle@610455d6t
cn.ethan.design.flyweight.CharacterStyle@610455d6x

从结果可以看出,同一种风格的字用的是同一个享元对象。

2.2 在String 常量池中的应用

从上一应用我们发现,很像Java String常量池的应用:对于创建过的String,直接指向调用即可,不需要重新创建。比如说这段代码:

String str1 = “abc”;
String str2 = “abc”;
String str3 = new String(“abc”);
String str4 = new String(“abc”);

在Java 运行时区域中:

2.3 在Java 包装类中的应用

在Java中有Short、Long、Byte、Integer等包装类。这些类中都用到了享元模式,以Integer 为例进行讲解。

在介绍前先看看这段代码:

Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2);
System.out.println(i3 == i4);

首先说明“==”是判断两个对象存储的地址是否相同

按照常理,最后输出应该都是true,然而最后的输出是:

true
false

这是因为Integer包装类型的自动装箱和拆箱、Integer中的享元模式的结果导致的。我们一步步来看:

2.3.1 包装类型的自动装箱(Autoboxing)和自动拆箱(Unboxing)

  1. 自动装箱

就是自动将基本数据类型装换成包装类型。实际上Integer i1 = 100底层是Integer i1 = Integer.valueOf(100)。看看这段源码:

/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);
}

说明在装箱时,看似相同的值,但是创建了两个不同的Integer对象,因此两个100的值自然不相同了。所以上面代码创建的对象每个都不相同,所以应该都是false呀,但为什么i1i2还是相同的呢?

我们再来看中间的这句话:

This method will always cache values in the range -128 to 127,

这个方法总是会缓存值在-128到127之间的值,

说明在[-128, 127]范围内的值,自动装箱不会创建对象,是利用享元模式进行共享。而IntegerCache就相当于生成享元对象的工厂类,我们再看其源码:

/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint 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\_VALUEh = 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() {}
}
  1. 自动拆箱

是自动将包装类型转换成基本数据类型。实际上int j1 = i1底层是int j1 = i1.intValue(),我们看其源码:

/**
* Returns the value of this {@code Integer} as an
* {@code int}.
*/
public int intValue() {return value;
}

实际上也就是直接返回该值。

回到上面的四行代码:

  • 前两行是因为它们的值在[-127, 128]之间,而且由于享元模式,i1i2共用一个对象,所以结果为true
  • 后两行则是因为它们值在范围之外,所以重新创建不同的对象,因此结果为false

其实在使用包装类判断值时,尽量不要使用“==”来判断,IDEA中也给我们提了醒:

所以说在判断包装类时,应该尽量使用"equals"来进行判断,先判断两者是否为同一类型,然后再判断其值

public boolean equals(Object obj) {if (obj instanceof Integer) {return value == ((Integer)obj).intValue();}return false;
}

所以对于上面的四行代码,最后的结果就都会是true了。

三、享元模式和单例模式、缓存的区别

3.1 和单例模式的区别

单例模式中,一个类只能创建一个对象,而享元模式中一个类可以创建多个类。享元模式则有点单例的变体多例。但是从设计上讲,享元模式是为了对象复用,节省内存,而多例模式是为了限制对象的个数,设计意图不相同。

3.2 和缓存的区别

在享元模式中,我们是通过工厂类来“缓存”已经创建好的对象,重点在对象的复用。

在缓存中,比如CPU的多级缓存,是为了提高数据的交换速率,提高访问效率,重点不在对象的复用

参考资料

《重学Java设计模式》

《设计模式之美》专栏

http://c.biancheng.net/view/1371.html

设计模式学习笔记(十二)享元模式及其应用相关推荐

  1. GoF设计模式(十二) - 享元模式

    前言 享元模式(Flyweight Pattern)运用共享技术有效地支持大量细粒度的对象,以减少内存占用和提高性能.这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的 ...

  2. 设计模式学习笔记(二)工厂模式、模板模式和策略模式的混合使用

    一.工厂模式(Factory pattern) 工厂模式又叫做工厂方法模式,是一种创建型设计模式,一般是在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型. 1.1 工厂模式介绍 工厂模式是 ...

  3. 拿捏大厂面试,设计模式学习笔记(二)工厂模式、模板模式和策略模式的混合使用

    一.工厂模式 工厂模式又叫做工厂方法模式,是一种创建型设计模式,一般是在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型. 1.1 工厂模式介绍 工厂模式是Java 中比较常见的一种设计模式 ...

  4. java 设计模式学习笔记十四 template模版模式

    /**  * 模版  *   * @time 下午09:58:00  * @author retacn yue  * @Email zhenhuayue@sina.com  */ public abs ...

  5. 设计模式(十)享元模式Flyweight(结构型)

    设计模式(十)享元模式Flyweight(结构型) 说明: 相对于其它模式,Flyweight模式在PHP实现似乎没有太大的意义,因为PHP的生命周期就在一个请求,请求执行完了,php占用的资源都被释 ...

  6. Java设计模式之结构型:享元模式

    一.什么是享元模式: 享元模式通过共享技术有效地支持细粒度.状态变化小的对象复用,当系统中存在有多个相同的对象,那么只共享一份,不必每个都去实例化一个对象,极大地减少系统中对象的数量.比如说一个文本系 ...

  7. Python语言入门这一篇就够了-学习笔记(十二万字)

    Python语言入门这一篇就够了-学习笔记(十二万字) 友情提示:先关注收藏,再查看,12万字保姆级 Python语言从入门到精通教程. 文章目录 Python语言入门这一篇就够了-学习笔记(十二万字 ...

  8. OpenCV学习笔记(十二)——图像分割与提取

    在图像处理的过程中,经常需要从图像中将前景对象作为目标图像分割或者提取出来.例如,在视频监控中,观测到的是固定背景下的视频内容,而我们对背景本身并无兴趣,感兴趣的是背景中出现的车辆.行人或者其他对象. ...

  9. java/android 设计模式学习笔记(8)---桥接模式

    这篇博客我们来介绍一下桥接模式(Bridge Pattern),它也是结构型设计模式之一.桥接,顾名思义,就是用来连接两个部分,使得两个部分可以互相通讯或者使用,桥接模式的作用就是为被分离了的抽象部分 ...

  10. 吴恩达《机器学习》学习笔记十二——机器学习系统

    吴恩达<机器学习>学习笔记十二--机器学习系统 一.设计机器学习系统的思想 1.快速实现+绘制学习曲线--寻找重点优化的方向 2.误差分析 3.数值估计 二.偏斜类问题(类别不均衡) 三. ...

最新文章

  1. 打一场AI竞赛,让你知道我的厉害
  2. MySQL创建相同表和数据命令
  3. 【NLP】如何利用BERT来做基于阅读理解的信息抽取
  4. Vim特定行行尾追加
  5. mysql schema设计_mongodb 的 schema 设计方法
  6. 后端拼接html能做判断吗,怎么判断是前端bug还是后端bug?
  7. TreeMap实现排序
  8. ctrl z撤销后如何恢复_回收站清空后数据如何恢复?
  9. 王者荣耀AI即将上线,队友再也不用担心你掉线了
  10. OpenCV图像处理(0)——文件夹批量读取文件
  11. c++自底向上算符优先分析_Python语言元素之运算符
  12. js生成java uuid_JS生成UUID
  13. 列宽一字符等于多少厘米_excel里面的列宽和行高单位是多少?多少等于1厘米?怎么对比的?...
  14. promise的意义和用法
  15. TiDB聚簇表和非聚簇表
  16. 云智巡在连锁药店的巡检作用
  17. 虚拟化技术的演变过程和KVM虚拟化的简介
  18. 液晶监控屏:大屏领域已占据主导地位
  19. sparksql insertinto 源码解析
  20. 小飞侠的游园方案答案

热门文章

  1. STM32之高级定时器
  2. ubuntu安装微信、foxmail、企业微信、qq、钉钉
  3. 位运算获取相反数详解
  4. 【计算机网络】【应用层概述】
  5. 20170117 机房『练习赛』
  6. unbuntu18.04-----bilibili网页端无法播放视频
  7. 【安卓逆向】护眼软件去广告教程(简单详细)软件名护眼宝
  8. 重庆成人自考本科考哪几门
  9. 三菱5uplc伺服电机指令_伺服电机控制的几个问题,搞清楚就能轻松入门!
  10. 开始学习 ajax 框架:Rialto