结构型设计模式——享元模式

news/2025/2/7 13:21:33/

享元模式

享元模式是结构型模式中最简单的一个模式,享是共享的意思,元是最小单元或细小的对象的意思。也就是说对一些需要大量重复使用的很细的对象进行缓存,缓存了就可以重复使用,例如Integer类中对整型-128到127进行了缓存,使用的正是享元模式。它有点类似单例模式,不过单例模式只共享一个类的唯一对象,而享元模式是共享多个类的唯一对象。

给出定义如下:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。用大白话讲就是如果一个项目中有很多很细很小的对象,那么如果对象太多那么会导致JVM内存利用率低下,所以需要对这些很零散的小对象进行统一管理。

享元(Flyweight )模式中存在以下两种状态:

  1. 内部状态,即不会随着环境的改变而改变的可共享部分,例如下面例子中俄罗斯方块对象中的shape属性就是内部状态,固定死的属性。避免重复创建对象,节省内存空间。根据内部状态把对象存储在共享池,需要时去共享池取就行。
  2. 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。例如,下面俄罗斯方块对象的颜色属性,每种类型的方块都可以有不同的颜色。允许外部进行修改。

内部状态可以理解为对象固有的属性,不能改变,例如俄罗斯方块对象的shape属性,而外部状态是随环境而变化的,例如在不同游戏背景下方块的颜色不一样。因此,对于不变的属性是可以共享的,而变化的属性例如颜色是不可共享的,因为假设现在同时开启了两个不同背景的俄罗斯方块游戏,那么方块的颜色应该是不一样的。

享元模式的主要有以下角色:

  • 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • 具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • 享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

案例:俄罗斯方块

俄罗斯方块这个游戏中,需要反复使用到有很多不同的方块(I型,L型,Z型等),每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,如果每次出现对象都重新new一个,那么堆上占用的空间就会很多,这里还好俄罗斯方块也就不超过十种,如果是类似于类似于数字游戏的对象呢?每个数字都是一个对象实例,这里仅用俄罗斯方块利用享元模式进行实现。

先来看类图:
在这里插入图片描述

代码实现

俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。

public abstract class AbstractBox {private Character shape;private String color;public AbstractBox(Character shape){this.shape = shape;}public Character getShape() {return shape;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}@Overridepublic String toString() {return "AbstractBox{" +"shape=" + shape +", color='" + color + '\'' +'}';}
}

接下来就是定义不同的形状了,IBox类、LBox类、ZBox类等。这些类对应着的概念就是元,享元就是共享(缓存)这些频繁创建的对象。

public class IBox extends AbstractBox{public IBox(){super('I');super.setColor("White");}
}public class LBox extends AbstractBox{public LBox(){super('L');super.setColor("White");}
}public class ZBox  extends  AbstractBox{public ZBox(){super('Z');super.setColor("White");}
}

提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。如此的话,需要方块对象只需要从单例工厂中获取即可,所获取到的对象都是缓存的,不会重复创建,节省大量空间。


public class BoxFactory {private static HashMap<Character,AbstractBox> map;private BoxFactory(){map = new HashMap<>();IBox iBox = new IBox();LBox lBox = new LBox();ZBox zBox = new ZBox();map.put('I',iBox);map.put('L',lBox);map.put('Z',zBox);}private static class SingletonHolder{// 静态内部类实现单例模式,如果不懂请学本系列的单例模式private static final BoxFactory INSTANCE = new BoxFactory();}public static final BoxFactory getInstance(){return SingletonHolder.INSTANCE;}public AbstractBox getBox(Character c){return map.get(c);}
}// 客户类,测试调用类
public class Main {public static void main(String[] args) {BoxFactory instance = BoxFactory.getInstance();AbstractBox ibox = instance.getBox('I');System.out.println(ibox);System.out.println(instance.getBox('Z'));System.out.println(instance.getBox('L'));}
}

优点

  • 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
  • 享元模式中的外部状态相对独立,且不影响内部状态

缺点

为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂。上面的俄罗斯方块的例子只考虑了内部状态,对外部状态没有考虑进去,如果考虑进去将会非常复杂。

使用场景

  • 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

JDK例子: Integer 使用了享元模式,默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象。

参考内容:
传智播客系列设计模式笔记
https://zhuanlan.zhihu.com/p/86135908
https://zhuanlan.zhihu.com/p/74872012
https://juejin.cn/post/7088505397639643173

建议阅读下一篇:结构型设计模式——外观模式


http://www.ppmy.cn/news/1295084.html

相关文章

Mybatis实现增删改查的两种方式-配置文件/注解

环境准备 1.数据库表tb_brand -- 删除tb_brand表 drop table if exists tb_brand; -- 创建tb_brand表 create table tb_brand(-- id 主键id int primary key auto_increment,-- 品牌名称brand_name varchar(20),-- 企业名称company_name varchar(20),-- 排序字段ordered int…

el-select下拉框 change事件返回该项所有数据

主要代码 value-key <template><div><el-selectv-model"value"value-key"label"placeholder"请选择"change"selectChange"><el-optionv-for"item in options":key"item.label":label"…

安卓Android Studio读写FM1208CPU卡源码

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?spma1z10.5-c-s.w4002-21818769070.11.6c46789elLwMzv&id615391857885 <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout x…

vue 使用 splice 删除元素UI视图不同步怎么办?

vue 使用 splice 删除元素UI视图不同步怎么办&#xff1f; https://www.zhihu.com/question/585036077?write#:~:text%E5%9C%A8Vue%E4%B8%AD%E4%BD%BF%E7%94%A8splice,%28%29%E6%96%B9%E6%B3%95%E5%88%A0%E9%99%A4%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0%E6%…

iPhone 恢复出厂设置后如何恢复数据

如果您在 iPhone 上执行了恢复出厂设置&#xff0c;您会发现所有旧数据都被清除了。这对于清理混乱和提高设备性能非常有用&#xff0c;但如果您忘记保存重要文件&#xff0c;那就是坏消息了。 恢复出厂设置后可以恢复数据吗&#xff1f;是的&#xff01;幸运的是&#xff0c;…

多参数函数或宏定义

多参数函数 typedef char *va_list; va_start 获取可变参数列表的第一个参数的地址 va_arg 获取可变参数的当前参数&#xff0c;返回指定类型并将指针指向下一参数 va_end宏 清空va_list可变参数列表 具体使用&#xff1a; //简陋实现printf void myPrint(const char* fm…

Qt/C++编写视频监控系统82-自定义音柱显示

一、前言 通过音柱控件实时展示当前播放的声音产生的振幅的大小&#xff0c;得益于音频播放组件内置了音频振幅的计算&#xff0c;可以动态开启和关闭&#xff0c;开启后会对发送过来的要播放的声音数据&#xff0c;进行运算得到当前这个音频数据的振幅&#xff0c;类似于分贝…

Matytype的安装问题(word及PPT报错问题)

特别针对&#xff1a;mathtype安装了多次&#xff0c;又卸载了多次的用户。 Word报弹错错误&#xff1a;参考 mathtype安装后&#xff0c;打开word出现没找到dll的错误&#xff0c;这个问题较好解决。 如何解决MathType兼容Office 2016-MathType中文网 PPT&#xff08;PowerPoi…