把书读薄 | 《设计模式之美》设计模式与范式(结构型-享元模式)(下)

简介: 本文对应设计模式与范式:结构型(54-55),享元模式 (Flyweight Pattern),结构型设计模式最后一个~

运行结果输出如下


网络异常,图片无法展示
|


使用享元模式只创建了4个卡牌对象,提提内部状态和外部状态的概念:


  • 内部状态固定不变可共享 的部分,存储在享元对象内部,比如这里的花色;


  • 外部状态可变不可共享 的部分,一般由客户端传入享元对象内部,比如这里的大小;


当然,状态的区分也不是绝对的,要看场景,比如扩展到斗地主的对局,内部状态就变成了54张牌(怎么发都不会超过54张),外部状态变成了牌的持有人。扩展到象棋棋局,内部状态(颜色、文字),外部状态(位置信息等)。


顺带带出UML类图和组成角色:


网络异常,图片无法展示
|


  • Flyweight (享元类) → 抽象类或接口,定义享元对象要实现的公共操作方法,方法会传入外部状态参数;


  • ConcreteFlyweight (具体享元类) → 实现享元接口,并为内部状态添加存储空间;


  • FlyweightFactory (享元工厂) → 创建并管理共享的享元对象,对外提供访问接口;


享元模式的本质


通过创建更多的可复用对象的共有特征,来尽可能减少创建重复对象的内存消耗。


缺点


时间换空间,对于需要快速响应的系统并不合适,需分离出内部和外部状态,难统一,增加了系统设计实现的复杂度;


0x2、享元模式 VS 多例、缓存、对象池


从代码实现上看,享元模式和前面学的 单例中的多例 很相似,但从设计意图上看,是完全不同的。


多例是为了限制对象的个数,享元模式则是为了对象复用,节省内存。


在来看看与缓存的区别:


享元模式强调的是空间效率(大数据模型对象复用),而缓存模式强调的是 时间效率(如缓存秒杀的活动数据和库存数据等,数据可能会占用大量空间,目的是为了及时响应)。


还有对象池:


为了避免对象频繁创建和释放导致内存碎片,预先申请一块连续的内存空间,每次创建对象时,直接从对象池里取出一个空闲对象来使用,使用完再重新放回对象池中以后后续使用,而非直接释放。


池化技术里的复用,可以理解为重复使用,主要目的是节省时间,任意时刻,每个对象都是被一个使用者独占。而享元模式的复用,可以理解为共享使用,主要目的是节省空间,在整个生命周期中,都是被所有使用者共享


0x3、享元模式在Java Integer、String中的应用


先说下Integer的,下面的代码:


Integer i1 = 12;
Integer i2 = 12;
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i1 == i2);   // 输出:true
System.out.println(i3 == i4);   // 输出: false


学过Java的都知道:**双等号(==)**区别对待两类数据类型:


  • 基本数据类型(byte、char、int等) → 比较值是否相同;


  • 引用数据类型(对象实例) → 比较变量指向的对象内存地址是否相同;


既然i1和i2相等,说明指向同一对象内存地址,存在对象重用,按理来说,i3和i4应该也相等啊?且听我娓娓道来:


执行到这样的语句:Integer i1 = 12,12是基本数据类型int,赋值给包装器类型Integer时,会触发自动装箱机制,底层相当于执行了这样的语句:


// 创建Integer类型的实例,并赋值
Integer i1 = Integer.valueOf(12);
// 附:自动拆箱 int j = i1 底层执行的是:
int j = i1.intValue();


行,那就来跟下 valueOf() 方法都做了啥:


网络异常,图片无法展示
|


判断是否大于low小于high,是返回缓存中的Integer实例,否则返回新的Integer实例,看下 IntegerCache


网络异常,图片无法展示
|


呕吼,当IntegerCache类被加载是,初始化了值在 -128-127 间的Integer实例,并加入到缓存数组中。而只缓存这个范围的原因:


不可能预先缓存好所有的整型值,只能选择对于大部分应用来说或最常用的整形值,即一个字节的大小。


最大值也可以通过修改JVM配置进行修改 (二选一):


-Djava.lang.Integer.IntegerCache.high=255
-XX:AutoBoxCacheMax=255


这就是上面的代码输出true和false的原因了(-128-127范围内复用,不在范围外的直接new),这里的IntegerCache就是享元模式中的享元工厂。


再接着看下String,下面这样的代码:


String s1 = "杰哥";
String s2 = "杰哥";
String s3 = "杰" + "哥";
String s4 = new String("杰哥");
System.out.println(s1 == s2);   // 输出: true
System.out.println(s1 == s3);   // 输出: true
System.out.println(s1 == s4);   // 输出: false


和Integer类的设计思想相似,JVM会专门开辟一块存储区来存储字符串常量(字符串常量池),跟Integer不同,无法事先知道要共享哪些字符串常量,只能第一次用到的时候存储到常量池中,后续用到直接引用,就无需重新创建了。解释下上述代码:


  • s1 == s2 → 编译时,"杰哥"被存储在常量池中,s1和s2的引用都指向常量池里的"杰哥";


  • s1 == s3 → 编译器优化,先把字符串拼接,再去常量池里查找字符串是否存在,存在让s3直接指向该字符串;


  • s1 != s4 → 不是显式赋值,编译器会在堆中重新分配一个区域来存储它的对象数据;


以上就是本节的全部内容,下节开始啃11种行为型设计模式,谢谢~


相关文章
|
3月前
|
设计模式 Java
Java设计模式-享元模式(12)
Java设计模式-享元模式(12)
|
4月前
|
设计模式 存储 Java
【十】设计模式~~~结构型模式~~~享元模式(Java)
文章详细介绍了享元模式(Flyweight Pattern),这是一种对象结构型模式,通过共享技术实现大量细粒度对象的重用,区分内部状态和外部状态来减少内存中对象的数量,提高系统性能。通过围棋棋子的设计案例,展示了享元模式的动机、定义、结构、优点、缺点以及适用场景,并探讨了单纯享元模式和复合享元模式以及与其他模式的联用。
【十】设计模式~~~结构型模式~~~享元模式(Java)
|
5月前
|
设计模式 存储 JavaScript
js设计模式【详解】—— 享元模式
js设计模式【详解】—— 享元模式
67 6
|
6月前
|
设计模式 缓存 Java
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
|
6月前
|
设计模式 存储 Java
Java设计模式之享元模式详解
Java设计模式之享元模式详解
|
6月前
|
设计模式
享元模式-大话设计模式
享元模式-大话设计模式
|
6月前
|
设计模式 程序员
结构型设计模式之适配器模式
结构型设计模式之适配器模式
|
27天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
3月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
29天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###