设计模式 -- 享元模式

386 阅读4分钟

享元模式

定义

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

**意图:**运用共享技术有效地支持大量细粒度的对象。

**主要解决:**在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用:  1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。

**如何解决:**用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

**关键代码:**用 HashMap 存储这些对象。

应用实例:  1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。

**优点:**大大减少对象的创建,降低系统的内存,使效率提高。

**缺点:**提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

使用场景:  1、系统有大量相似对象。 2、需要缓冲池的场景。

注意事项:  1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。

个人理解:本质是池化思想的理解

核心:

  • 外在状态存储:

    享元模式前的聚合对象

  • 享元与不可变性

    由于享元对象可在不同的情景中使用, 你必须确保其状态不能被修改。 享元类的状态只能由构造函数的参数进行一次性初始化, 它不能对其他对象公开其设置器或公有成员变量。

  • 享元工厂

    为了能更方便地访问各种享元, 你可以创建一个工厂方法来管理已有享元对象的缓存池。 工厂方法从客户端处接收目标享元对象的内在状态作为参数, 如果它能在缓存池中找到所需享元, 则将其返回给客户端; 如果没有找到, 它就会新建一个享元, 并将其添加到缓存池中。

    你可以选择在程序的不同地方放入该函数。 最简单的选择就是将其放置在享元容器中。 除此之外, 你还可以新建一个工厂类, 或者创建一个静态的工厂方法并将其放入实际的享元类中。

    对类图的说明

image.png 对原理图的说明-即(模式的角色及职责)

-   FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态(后面介绍) 的接口或实现
-   ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
-   UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
-   FlyWeightFactory 享元工厂类,用于构建一个池容器(集合), 同时提供从池中获取对象方法

image-20210729003735122

代码案例

  • java.lang.Integer#valueOf(int)

    image-20210729004725067

    image-20210729010026763

    只要给定的 i 在 low-high 的范围内,就会从一个 IntegerCache中获取一个 Integer 对象,否则将新创建一个并返回。

    可以看出 IntegerCache 是 Integer 中的一个内部类,在内部声明了一个 Integer数组,在静态代码块中给数组中赋值。

应用场景

仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。

应用该模式所获的收益大小取决于使用它的方式和情景。 它在下列情况中最有效:

  • 程序需要生成数量巨大的相似对象
  • 这将耗尽目标设备的所有内存
  • 对象中包含可抽取且能在多个对象间共享的重复状态。

源码分析

应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式

优缺点

  • 节省大量内存。
  • 如果程序中有很多相似对象, 那么你将可以节省大量内存。
  • 你可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据。
  • 代码会变得更加复杂。