设计模式轻松学【十九】享元模式

简介: 享指的是共享,String常量池、数据库连接池、线程池等都是享元模式的具体应用。

特点与定义

  • 定义:

    运用共享技术来有効地支持大量细粒度对象的复用,是一种对象结构型模式。

    享元模式要求能够共享的对象必须是细粒度对象。它通过共享已经存在的又橡来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

  • 问题由来

    在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。

  • 享元模式状态

    在了解享元模式之前我们先要了解两个概念:内部状态、外部状态。

    • 内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
    • 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。

    享元模式的实现关键就是区分应用中的这两种状态,并将外部状态外部化

  • 参与角色

    • Flyweight(抽象享元角色):定义了具体享元类的公共行为,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态),非享元的外部状态以参数的形式通过方法传入。
    • Concrete Flyweight(具体享元)角色:实现抽象享元角色中所规定的接口,为享元对象提供了内存空间来保存享元对象的内部数据,通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
    • Unsharable Flyweight(非共享享元)角色:并不是所有的享元类都需要被共享的,有的享元类就不要被共享,可以通过享元类来实例一个非共享享元对象。是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
    • Flyweight Factory(享元工厂)角色:负责创建和管理享元对象。它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
  • 类结构图

    image.png

  • 结构代码示例

    在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。典型的享元工厂类的代码如下:

    • 定义享元抽象(Flyweight)

      //抽象享元角色
      abstract class Flyweight {
          //内部状态
          public String intrinsic;
          
          //外部状态
          protected UnsharableFlyweight extrinsic;
          
          //要求享元角色必须接受外部状态
          public void setExtrinsic(UnsharableFlyweight extrinsic) {
              this.extrinsic = extrinsic;
          }
           
          //定义一些公共行为,这些行为可以来设置或提供(内部、外部状态)
          public abstract void operation();
      }
    • 定义具体享元(ConcreteFlyweight)

      class ConcreteFlyweight extends Flyweight{
          public void operation() {
              System.out.println("具体享元对象");
          }
      }
    • 定义非共享享元

      class UnsharableFlyweight {
          int x;
          int y;
          public UnsharableFlyweight(int x, int y) {
              this.x = x;
              this.y = y;
          }
      }
    • 定义享元工厂

      class FlyweightFactory {
      
          //定义一个池容器
          private static Map<String, Flyweight> pool = new HashMap<>();
          
          //享元工厂
          public static Flyweight getFlyweight(String key) {
              Flyweight flyweight = null;
              
              if(pool.containsKey(key)) {    //池中有该对象
                  flyweight = pool.get(key);
                  System.out.print("已有 " + key + " 直接从池中取---->");
              } else {
                  //根据外部状态创建享元对象
                  flyweight = new ConcreteFlyweight();
                  //放入池中
                  pool.put(key, flyweight);
                  System.out.print("创建 " + key + " 并从池中取出---->");
              }
              return flyweight;
          }
      }
    • 客户端调用代码

      public class FlyweightTest {
      
          public static void main(String[] args) {
              String key = "first";
              //第一次获取
              Flyweight flyweight1 = FlyweightFactory.getFlyweight(key);
              flyweight1.setExtrinsic(new UnsharableFlyweight(10,20));
              System.out.println(flyweight1);
              
              //第二次获取
              Flyweight flyweight2 = FlyweightFactory.getFlyweight(key);
              flyweight2.setExtrinsic(new UnsharableFlyweight(30,20));
              System.out.println(flyweight2);
          }
      }

模式案例分析

现在我们使用享元模式实现如下功能:每瓶冰红茶饮料盖上都会有一个二维码,扫描二维码就知道是否中奖,我们简化二维码为中奖文字,都会在瓶盖上,瓶盖的形状和颜色是可以提取为相似的部分的,然后中奖信息每个瓶盖都不一样,所以为有差异的非共享成员。

  • 定义抽象享元接口

    abstract class RedTea {
        
        //内部状态,容量
        public String capacity;
        
        //外部状态,二维码内容
        protected QRCodeContent extrinsic;
        
        //要求享元角色必须接受外部状态
        abstract String viewQRCodeContent(QRCodeContent extrinsic);
        
    }
  • 定义非共享对象接口

    //非共享角色,二维码内容,每瓶都不一样
    class QRCodeContent {
        String qrCode;
        
        public QRCodeContent() {
            qrCode = UUID.randomUUID().toString();
        }
        
        public String wrapperContent() {
            qrCode = "二维码唯一ID:" + qrCode + "\n" + "根据ID得到中奖信息:" +  (new Random().nextInt(6) + 1)  + "等奖";
            return qrCode;
        }
    }
  • 定义具体享元接口

    class ConcreteRedTea extends RedTea{
        
        public ConcreteRedTea(String capacity) {
            this.capacity = capacity;
        }
    
        @Override
        String viewQRCodeContent(QRCodeContent extrinsic) {
             return "茶壶容量:" + this.capacity + "\n" + "二维码内容:" + extrinsic.wrapperContent();
        }
    }
  • 定义享元工厂

    class FlyweightFactory {
    
        //定义一个池容器
        private static Map<String, RedTea> pool = new HashMap<>();
        
        //享元工厂
        public static RedTea getFlyweight(String capacity) {
            if(pool.containsKey(capacity)) {    //池中有该对象
                return pool.get(capacity);
            } else {
                //放入池中
                pool.put(capacity, new ConcreteRedTea(capacity));
                return pool.get(capacity);
            }
        }
    }
  • 客户端调用

    public class Client {
    
        public static void main(String[] args) {
            String capacity = "1.5L";
            //第一次获取
            RedTea flyweight1 = FlyweightFactory.getFlyweight(capacity);
            String viewQRCode1 = flyweight1.viewQRCodeContent(new QRCodeContent());
            System.out.println(viewQRCode1);
            
            //第二次获取
            RedTea flyweight2 = FlyweightFactory.getFlyweight(capacity);
            String viewQRCode2 = flyweight2.viewQRCodeContent(new QRCodeContent());
            System.out.println(viewQRCode2);
        }
    }

所谓的享元模式就是利用共享池技术,将重复对象存储起来(内部状态),不同的因素由外部传入(外部状态),充分提高对象的重复利用率,提高性能

总结

  • 优点

    • 享元模式的优点在于它能够极大的减少系统中对象的个数。
    • 享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
  • 缺点

    • 由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。
    • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

适用场景

  1. 如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。
  2. 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

补充

  1. 享元模式可以极大地减少系统中对象的数量。但是它可能会引起系统的逻辑更加复杂化。
  2. 享元模式的核心在于享元工厂,它主要用来确保合理地共享享元对象。
  3. 内部状态为不变共享部分,存储于享元享元对象内部,而外部状态是可变部分,它应当由客户端来负责。
相关文章
|
17天前
|
设计模式 存储 Java
23种设计模式,享元模式的概念优缺点以及JAVA代码举例
【4月更文挑战第6天】享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享技术有效地支持大量细粒度对象的重用。这个模式在处理大量对象时非常有用,特别是当这些对象中的许多实例实际上可以共享相同的状态时,从而可以减少内存占用,提高程序效率
31 4
|
6月前
|
设计模式 存储 缓存
结构型设计模式07-享元模式
结构型设计模式07-享元模式
20 0
|
4月前
|
设计模式 存储 安全
二十三种设计模式全面解析-享元模式(Flyweight Pattern)详解:构建高效共享的对象结构
二十三种设计模式全面解析-享元模式(Flyweight Pattern)详解:构建高效共享的对象结构
|
4月前
|
设计模式
二十三种设计模式全面解析-组合模式与享元模式的结合应用:实现对象的共享和高效管理
二十三种设计模式全面解析-组合模式与享元模式的结合应用:实现对象的共享和高效管理
|
6月前
|
设计模式 存储 Java
【设计模式——学习笔记】23种设计模式——享元模式Flyweight(原理讲解+应用场景介绍+案例介绍+Java代码实现)
【设计模式——学习笔记】23种设计模式——享元模式Flyweight(原理讲解+应用场景介绍+案例介绍+Java代码实现)
28 0
|
8天前
|
设计模式 存储 Java
小谈设计模式(27)—享元模式
小谈设计模式(27)—享元模式
|
1月前
|
设计模式 缓存 Java
设计模式之享元模式
设计模式之享元模式
|
1月前
|
设计模式 存储 缓存
【设计模式】享元模式
【设计模式】享元模式
|
3月前
|
设计模式 存储 缓存
聊聊Java设计模式-享元模式
享元(Flyweight)模式:顾名思义就是**被共享的单元**。意图是复用对象,节省内存,提升系统的访问效率。比如在红白机冒险岛游戏中的背景花、草、树木等对象,实际上是可以多次被不同场景所复用共享,也是为什么以前的游戏占用那么小的内存,却让我们感觉地图很大的原因。
17 3
聊聊Java设计模式-享元模式
|
3月前
|
设计模式 存储 Go
Golang设计模式——16享元模式
Golang设计模式——16享元模式
23 0