22种设计模式——享元模式

1. 概述

享元模式是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。

在这里插入图片描述
从问题引入,假如你希望在长时间工作后放松一下, 所以开发了一款简单的游戏: 玩家们在地图上移动并相互射击。 你决定实现一个真实的粒子系统, 并将其作为游戏的特色。 大量的子弹、 导弹和爆炸弹片会在整个地图上穿行, 为玩家提供紧张刺激的游戏体验。

尽管该游戏在你的电脑上完美运行, 但是你的朋友却无法长时间进行游戏: 游戏总是会在他的电脑上运行几分钟后崩溃。 在研究了几个小时的调试消息记录后, 你发现导致游戏崩溃的原因是内存容量不足。 朋友的设备性能远比不上你的电脑, 因此游戏运行在他的电脑上时很快就会出现问题。

真正的问题与粒子系统有关。 每个粒子 (一颗子弹、 一枚导弹或一块弹片) 都由包含完整数据的独立对象来表示。 当玩家在游戏中鏖战进入高潮后的某一时刻, 游戏将无法在剩余内存中载入新建粒子, 于是程序就崩溃了。
在这里插入图片描述
那该怎么办呢?该怎么办也让朋友的拉跨电脑也能玩这个游戏呢?既然内存不够,那我就看能不能减少点内存使用。很快哈,你就会发现原来每一个子弹的对象很多东西其实都是一样的,除了可能位置、颜色等属性不一样,其他内容都是一样的,所以你可能就有了一个想法:我能不能复用这些属性,每次使用的时候只修改一些特有属性,从而达到节省内存花销。

如果你有这个想法,恭喜你你有了享元模式的思想。是的享元模式一大特点就是共享了对象的部分内容,把一个对象分为了两部分,同时这个被分成两部分的对象也叫做享元对象。

  • 外部状态:对象可变的状态,不复用的部分,像上面问题中的子弹的颜色、位置
  • 内部状态:对象不变或者十分稳定的状态,可复用的部分

那又该怎么实现享元模式呢,或者说该怎么实现对象的复用呢。其实说到底,享元模式的一个核心就是使用了复用技术,其实这种使用在我们日常是很常见的,比如JDBC的连接池、多线程的线程池,都是复用的技术。那我们可不可以借鉴一下,也是用一个池,用来存放以及共享的对象。

事实上,享元模式的确就是这么做的,享元模式和缓存池的实现很像,但享元模式里不叫池叫做享元工厂,这个工厂缓存了内部状态,让它得以被复用,所以知道池技术的小伙伴享元模式应该很好理解。具体看下面的代码实现。

  • 总结
    1. 享元模式的核心就是实现了对象的复用,在某种角度上来说就是实现了一个单例的内部状态(享元模式不是单例模式)
    2. 享元模式的实现和我们日常所见的池技术很像,建一个类存放共享对象,每次需要的时候就向这个类申请
    3. 享元对象把一个类分成两部分,一部分为不稳定的状态,叫外部状态,不可共享;一部分很稳定,叫内部状态,可共享
    4. 享元模式仅在程序必须支持大量对象且没有足够的内存容量时,否则会造成逻辑的混乱

2. 特点

  • 优点: 如果程序中有很多相似对象, 那么你将可以节省大量内存。

  • 缺点

    1. 你可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据。
    2. 代码会变得更加复杂。 团队中的新成员总是会问: ​ “为什么要像这样拆分一个实体的状态?”
  • 使用场景:仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。应用该模式所获的收益大小取决于使用它的方式和情景。 它在下列情况中最有效:

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

3. 实现

下面举一个例子来实现,植树节来了,现在要植树小程序,我们都知道,大多数树长得其实都差不多,所以如果每次都单独建一个树的对象,其实很浪费内存,所以使用享元模式来实现。

把树看成享元对象,分成内部状态TreeInside,这个包含了树的种类;外部状态TreeOutside,这部分包含了树的高度和宽度。具体实现看下文:

  • UML类图
    在这里插入图片描述

  • 角色

    1. 内部状态:享元对象的共享部分,内部状态要依赖外部状态,有时看需求也可以改成组合,反正就是要建立起与外部状态的联系,组成一个完整的享元对象。

    2. 外部状态:享元对象的不可共享部分

    3. 享元工厂:不用把享元工厂看的那么复杂,其实他就是个生产内部状态的缓存池,所以它与内部状态有关系,和外部状态没关系

    4. 客户端:调用享元模式的角色

  • Java实现

    1. 内部状态

      /**
       * @Author: chy
       * @Description: 享元对象的内部状态,树的种类
       * @Date: Create in 14:23 2021/3/15
       */
      public class TreeInside {
          private String name;
      
          public TreeInside(String name) {
              this.name = name;
          }
      
          public void plant(TreeOutside treeOutside){
              System.out.println("种一棵高为"+treeOutside.getTall()+"米宽为"+treeOutside.getWidth()+"米的"+name);
          }
      }
      
    2. 外部状态

      /**
       * @Author: chy
       * @Description: 享元对象的外部状态,树的高度和宽度
       * @Date: Create in 14:24 2021/3/15
       */
      public class TreeOutside {
          private int tall;
          private int width;
      
          public TreeOutside(int tall, int width) {
              this.tall = tall;
              this.width = width;
          }
      
          public int getTall() {
              return tall;
          }
      
          public int getWidth() {
              return width;
          }
      }
      
    3. 享元工厂

      /**
       * @Author: chy
       * @Description: 享元工厂,存放享元对象的内部状态:树的生产工厂
       * @Date: Create in 14:27 2021/3/15
       */
      public class TreeFactory {
          private static HashMap<String,TreeInside> pool = new HashMap<>();
      
          public static TreeInside getTree(String name){
              if (!pool.containsKey(name)){
                  pool.put(name,new TreeInside(name));
              }
              return pool.get(name);
          }
      }
      
    4. 客户端

      /**
       * @Author: chy
       * @Description: 客户端
       * @Date: Create in 15:11 2021/3/13
       */
      public class Client {
          public static void main(String[] args) {
              // 种十棵橡树 和 十
              for (int i = 0; i < 10; i++) {
                  TreeInside treeInside1 = TreeFactory.getTree("橡树");
                  treeInside1.plant(new TreeOutside(i+1,i+1));
              }
      
              System.out.println("========================================");
      
              // 种十棵榕树
              for (int i = 0; i < 10; i++) {
                  TreeInside treeInside2 = TreeFactory.getTree("榕树");
                  treeInside2.plant(new TreeOutside(i+1,i+1));
              }
          }
      }
      
    5. 结果
      在这里插入图片描述

实际上,享元模式远没有上面这么简单,享元模式的实现算是一个比较复杂的东西,但上面的大家意会就好,总结一下,就几句话:享元模式把一个类分成两个部分,一部分可共享一部分不可共享,两部分通过依赖或者组合的关系关联起来,可共享的部分建一个类似于缓存池的类存起来,这样就实现类复用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值