代码改变世界

结构类模式(六):享元(Flyweight)

2016-10-27 14:20  阿诚de窝  阅读(229)  评论(0)    收藏  举报

定义

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

两个状态

  1. 内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的。
  2. 外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。

和对象池的区别

  1. 对象池主要解决对象的复用问题,池中的每个对象都是可以替换的,从池中获取对象A和获取对象B对客户端而言都是一样的。
  2. 享元模式主要解决对象的共享问题,建立多个细粒度的可以进行共享的对象是其关注的重点。

UML

优点

  1. 减少运行时对象实例的个数,节省内存。
  2. 将许多“虚拟”对象的状态集中管理。

缺点

  1. 一旦你实现了它,单个的逻辑实现将无法拥有独立而不同的行为。

应用场景

  1. 系统中有大量对象且这些对象消耗大量内存。
  2. 这些对象的状态大部分可以外部化。
  3. 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。

示例

我们考虑一个场景,当我们需要自己实现绘制艺术字时,每一个字符都会有下面的几个状态:1.字体轮廓(这部分就是最耗内存的部分,每个字体都会有大量的数据来记录该字体的样式);2.字体字符(标志该字体用来表示哪一个字符);3.字体大小;4.字体位置。

当我们使用该艺术字来呈现一堆文本时,如果每个字符都加载同一份轮廓则会对内存和GC造成压力。

我们会发现对于同一个字符来说,轮廓和字符都是一致的内部状态,而大小和位置则是会变动的外部状态;我们通过使用享元模式来节省内存的使用;

Java

 1 import java.util.HashMap;
 2 import java.util.Map;
 3 
 4 public class Main
 5 {
 6     public static void main(String[] args)
 7     {
 8         String text = "Hello World! Hello Flyweight!";
 9         for (int i = 0; i < text.length(); i++)
10         {
11             char c = text.charAt(i);
12             FontFlyweight font = (FontFlyweight) FontFlyweightFactory.getFont(c);
13             font.size = (int) ((Math.random() * 20) + 7);
14             font.x = (int) ((Math.random() * 500));
15             font.y = (int) ((Math.random() * 500));
16             font.drawFont();
17         }
18         FontFlyweightFactory.printFontCount();
19     }
20 
21     /**
22      * 字体享元接口
23      */
24     public interface IFontFlyweight
25     {
26         /**
27          * 绘制当前字体
28          */
29         void drawFont();
30     }
31     
32     /**
33      * 字体享元类
34      */
35     public static class FontFlyweight implements IFontFlyweight
36     {
37         /**
38          * 不会改变可以共享的内部状态
39          */
40         public final char symbol;
41         public final String font;
42 
43         /**
44          * 频繁改变不可以共享的外部状态
45          */
46         public int size;
47         public int x;
48         public int y;
49         
50         public FontFlyweight(char symbol)
51         {
52             this.symbol = symbol;
53             //这里一般会从硬盘读取指定的美术字的字体样式
54             this.font = "自定义的美术字";
55         }
56 
57         @Override
58         public void drawFont()
59         {
60             //实际上绘制一个指定样式的矢量字符是非常复杂的,内存中需要保存该字符的所有矢量信息,会占用比较大的内存开销
61             System.out.println("在位置(" + x + ", " + y + ")处使用字体\"" + font + "\"绘制\"" + size + "\"号尺寸的字符\"" + symbol + "\"");
62         }
63     }
64 
65     /**
66      * 字体享元工厂
67      */
68     public static class FontFlyweightFactory
69     {
70         private static Map<Character, IFontFlyweight> _map = new HashMap<>();
71 
72         /**
73          * 根据外部状态获取对应的享元对象
74          */
75         public static IFontFlyweight getFont(char key)
76         {
77             if(_map.containsKey(key))
78             {
79                 return _map.get(key);
80             }
81             IFontFlyweight info = new FontFlyweight(key);
82             _map.put(key, info);
83             return info;
84         }
85 
86         public static void printFontCount()
87         {
88             System.out.println("内存中存在的字体实例数量:" + _map.values().size());
89         }
90     }
91 }
View Code