前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >享元模式浅析

享元模式浅析

作者头像
孟君
发布2020-07-14 10:08:08
3890
发布2020-07-14 10:08:08
举报
文章被收录于专栏:孟君的编程札记

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

一. 享元模式的基本介绍

意图

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

结构

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

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

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

二. 享元模式的示例

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

  • TeaOrder.java(抽象享元角色)
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.flyweight;

public abstract class TeaOrder {

  public abstract void serveTea(TeaContext context);
}
  • Tea.java(具体享元角色)
代码语言:javascript
复制
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(非共享享元角色)
代码语言:javascript
复制
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(享元工厂)
代码语言:javascript
复制
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(客户端)
代码语言:javascript
复制
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() +  "]个");
  }
}

输出结果:

代码语言:javascript
复制
周末天气不错,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,先来看个例子:

代码语言:javascript
复制
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. 设计模式-可复用面向对象软件的基础. 机械工业出版社.

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟君的编程札记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档