享元模式浅析

面向对象技术可以很好地解决一些灵活性或可以扩展性问题,但是很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致对象创建以及垃圾回收的代价过高,造成性能下降等问题。

一. 享元模式的基本介绍

意图

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

结构

享元模式的基本结构如下:

这里涉及到的参与者有如下几种:

  • 抽象享元角色(Flyweight)
    • 此角色是所有的具体享元的超超类,为这些类规定出需要实现的公共接口。那些需要外部状态的操作可以通过方法的参数传入
  • 具体享元角色(ConcreteFlyweight)
    • 实现抽象享元角色所规定的接口。如果有内部状态的话,必须负责为内部状态提供存储空间。享元对象的内部状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内部共享。
  • 非共享享元角色(UnsharedConcreteFlyweight)
    • 并非所有Flyweight的子类都需要被共享。Flyweight接口使共享成为可能,但它并不是强制共享。在Flyweight对象结构的某些层次,UnsharedFlyweight对象通常将ConcreteFlyweight对象作为子节点。
  • 享元工厂(FlyweightFactory)
    • 创建并管理享元对象。
    • 确保合理地共享Flyweight。当用户请求一个Flyweight的时候,享元工厂对象提供一个已创建的实例或者创建一个(如果不存在的话)
  • 客户端(Client)
    • 维持一个对Flyweight对象的引用。
    • 计算或者存储一个(多个)Flyweight的外部状态。

二. 享元模式的示例

接下来,我们以一群好朋友周末去西湖游玩,然后在喜欢边茶馆喝茶闲聊为场景,,给出一个简单的享元模式示例。

  • TeaOrder.java(抽象享元角色)
package com.wangmengjun.tutorial.designpattern.flyweight;

public abstract class TeaOrder {

  public abstract void serveTea(TeaContext context);
}
  • Tea.java(具体享元角色)
package com.wangmengjun.tutorial.designpattern.flyweight;

public class Tea extends TeaOrder{

  private String flavor;
  
  public Tea(String flavor) {
    super();
    this.flavor = flavor;
    System.out.println("创建Tea对象,falvor为" + flavor);
  }

  @Override
  public void serveTea(TeaContext context) {
    System.out.println("向"+ context.getTable() +"桌提供一杯[" +flavor +"]");
  }

  /**
   * @return the flavor
   */
  public String getFlavor() {
    return flavor;
  }

  
}
  • TeaContext.java(非共享享元角色)
package com.wangmengjun.tutorial.designpattern.flyweight;

public class TeaContext {

  private  int tableNumber;
   
  public TeaContext(int tableNumber) {
    this.tableNumber = tableNumber;
  }
   
  public int getTable() {
    return this.tableNumber;
  }
}
  • TeaFcatory.java(享元工厂)
package com.wangmengjun.tutorial.designpattern.flyweight;

import java.util.HashMap;
import java.util.Map;

public class TeaFactory {

  private Map<String, Tea> flavorsMap = new HashMap<String, Tea>();
  
  public Tea getTeaFlavor(String flavor) {
    Tea tea = flavorsMap.get(flavor);
    if (tea == null) {
      tea = new Tea(flavor);
      flavorsMap.put(flavor, tea);
    }
    return tea;
  }
  
  public int getTotalTeaFlavorsMade() {
    return flavorsMap.size();
  }
  
}
  • Client.java(客户端)
package com.wangmengjun.tutorial.designpattern.flyweight;

public class Client {

  public static void main(String[] args) {
    System.out.println("周末天气不错,10个小伙伴一起到西湖边游玩~~~");
    System.out.println("大家在西湖边的一个茶馆坐下来,点了10杯茶,然后一起闲聊");
    System.out.println("其中1号桌子坐了4个人");
    System.out.println("其中2号桌子坐了3个人");
    System.out.println("其中3号桌子坐了3个人");
    
    System.out.println("可选的茶有3种:龙井、普洱和碧螺春");
    
    TeaContext table1 = new TeaContext(1);
    TeaContext table2 = new TeaContext(2);
    TeaContext table3 = new TeaContext(3);
    
    TeaFactory teaFactory = new TeaFactory();
    teaFactory.getTeaFlavor("龙井").serveTea(table1);
    teaFactory.getTeaFlavor("普洱").serveTea(table1);
    teaFactory.getTeaFlavor("龙井").serveTea(table1);
    teaFactory.getTeaFlavor("普洱").serveTea(table1);  
    
    teaFactory.getTeaFlavor("龙井").serveTea(table2);
    teaFactory.getTeaFlavor("普洱").serveTea(table2);
    teaFactory.getTeaFlavor("碧螺春").serveTea(table2);
  
    teaFactory.getTeaFlavor("碧螺春").serveTea(table3);
    teaFactory.getTeaFlavor("龙井").serveTea(table3);
    teaFactory.getTeaFlavor("碧螺春").serveTea(table3);  
  
    System.out.println("不同口味的茶对象一共创建了[" + teaFactory.getTotalTeaFlavorsMade() +  "]个");
  }
}

输出结果:

周末天气不错,10个小伙伴一起到西湖边游玩~~~
大家在西湖边的一个茶馆坐下来,点了10杯茶,然后一起闲聊
其中1号桌子坐了4个人
其中2号桌子坐了3个人
其中3号桌子坐了3个人
可选的茶有3种:龙井、普洱和碧螺春
创建Tea对象,falvor为龙井
向1桌提供一杯[龙井]
创建Tea对象,falvor为普洱
向1桌提供一杯[普洱]
向1桌提供一杯[龙井]
向1桌提供一杯[普洱]
向2桌提供一杯[龙井]
向2桌提供一杯[普洱]
创建Tea对象,falvor为碧螺春
向2桌提供一杯[碧螺春]
向3桌提供一杯[碧螺春]
向3桌提供一杯[龙井]
向3桌提供一杯[碧螺春]
不同口味的茶对象一共创建了[3]个

至此一个简单的享元模式例子就完成了。从上面输出的结果来看,10个人点了十杯茶,包括3中口味,我们只创建了3个茶口味的对象,有7杯茶用到的Tea对象来自于共享。

其实针对这种对象共享或者缓存起来,我们在JDK的源码中也能看到很多。比如Integer,先来看个例子:

package com.wangmengjun.tutorial.designpattern.flyweight;

public class IntegerTest {
  
  public static void main(String[] args) {
    Integer v1 = 100;
    Integer v2 = 100;
    //true
    System.out.println(v1 == v2);
    
    Integer v3 = 150;
    Integer v4 = 150;
    //false
    System.out.println(v3 == v4);
  }

}

第一个输出true,是因为-128到127的数直接取自Cache,所以是同一个对象。

三. 小结

优缺点

优点:

1、大幅度降低内存中对象的数量。

缺点:

1、享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。

2、享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

适合场景:

当以下所有条件都满足的时,可以考虑使用享元模式:

1、一个系统有大量的对象

2、完全由于使用大量的对象,造成很大的存储开销

3、对象的大多数状态都可以变为外部状态。

4、这些对象可以按照内部状态分成很多组,如果剔除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象

5、软件系统不依赖于这些对象的身份,也就是说,这些对象可以是不可分辨的。

参考

[1]. 阎宏. Java与模式.电子工业出版社

[2]. Erich Gamma. 设计模式-可复用面向对象软件的基础. 机械工业出版社.

本文分享自微信公众号 - 孟君的编程札记(gh_0f0f5e0ae1de),作者:孟君

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 门面模式浅析

    在生活中,比如在医院有接待员帮助病人完成门诊、挂号、付费以及取药,病人只接触接待员即可,由接待员负责与医院的各个部门打交道。

    孟君
  • 一步步完成jsRender + Spring MVC + Nginx前后端分离示例

    本篇博文的目标是使用前端页面渲染插件jsRender做前后端分离,后端采用Spring MVC给出REST API,并结合Nginx完成配置。

    孟君
  • 可视化排序实践之冒泡排序

    这样,一个简单地,模拟执行结合排序变化的例子就完成了。:) 有兴趣的读者可以试一试。

    孟君
  • Java transient关键字使用小记

    Java学习123
  • Java原型模式(prototype)

      prototype模式也就是原型模式,是javaGOF23种设计模式中的一种,我们在学习spring的时候在bean标签的学习中碰到过,所以本文来给大家介绍...

    用户4919348
  • 第22次文章:建造者模式+原型模式

    这周我们就可以把GOFO23设计模式中的创建型模式全部介绍完了!后面在项目里面可以试一下啦!

    鹏-程-万-里
  • Spring Boot 2.X(十八):集成 Spring Security-登录认证和权限控制

    在企业项目开发中,对系统的安全和权限控制往往是必需的,常见的安全框架有 Spring Security、Apache Shiro 等。本文主要简单介绍一下 Sp...

    朝雾轻寒
  • Spring JdbcTemplate 使用及持久层继承JdbcDaoSupport XML配置

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    多凡
  • Lombok中关于@Data的使用

    当你在使用 Lombok 的 @Data 注解时,其实会有一些坑需要关注,今天就让我们来见识一下。

    健程之道
  • java对象和类

    现在让我们深入了解什么是对象。看看周围真实的世界,会发现身边有很多对象,车,狗,人等等。所有这些对象都有自己的状态和行为。

    用户7657330

扫码关注云+社区

领取腾讯云代金券