Flyweight Pattern 享元模式作为结构型的设计模式,其通过共享来解决大量细粒度对象的复用问题。Flyweight一词在拳击比赛中指的是特轻量级,故这里采用意译”享元”来更好的表达该设计模式的作用
现实世界的指引
对一本英文书来说,其所用到的英文字母的种类数肯定不超过26x2个(区分大小写)。现在我们期望这本书变的更美丽一些,让每个字符都使用不同的颜色。例如对于书中的单词”assess”而言,我们期望a字符被印刷为白色,第一个s字符被印刷为红色,第二个s字符被印刷为黑色,……,第四个s字符被印刷为绿色。当我们使用活字印刷术时,实际上也只需要这52个不同英文字符的字块即可。至于同一种字符下不同的颜色的印刷效果,则可以在每次使用该字符的字块前先对其进行染色即可。而不会就同一种字符制造出各种颜色版本的字块,因为这样的话直接导致字块的数量爆炸
模式思想
通过前面例子我们实际上可以发现,在软件开发中我们同样会经常遇到这样的场景,我们需要某个类大量的对象实例,而这些实例很相似、相互之间只是有些细微的差别。这个时候,我们就可以通过分析,将对象的属性分为两种类型:内部状态、外部状态。前者变化有限,而后者变化繁多。将不同内部状态的对象实例通过共享的方式提供给client,来避免频繁创建对象实例。至于外部状态则是在client每次获取到具有某种内部状态的对象实例后,client自行按需设定的。这就是所谓的享元模式——即共享细粒度的对象
- 抽象享元角色:其定义了具体享元对象需要实现的方法,包括对于外部状态的设置等。即下面例子中的ShapeFlyweight接口
- 具体享元角色:其是抽象享元角色的具体实现。对于内部状态相同的具体享元角色,其是可以被共享的,即一次构造、重复使用。如下面例子中的Shape类
- 非共享的具体享元角色:其虽然也是抽象享元角色的具体实现。但是其实例一般是无法共享的。由于该对象内部通常持有了多个具体享元角色,而后者则是可以共享的。故该角色通常又被称为复合享元角色。如下面例子中的Shapes类
- 享元工厂角色:不论是具体享元角色还是非共享的具体享元角色,都不允许client直接创建相关实例。而是通过该工厂来创建、管理享元角色。具体地,当client需要一个享元对象时,首先会进行检查是否曾经创建过该对象,如果有,则不再重复创建,直接从缓存池中取出给client;如果没有,才会去创建返回给client,并将其放入缓存池中以便于日后的共享复用。如下面例子中的ShapeFlyweightFactory类
实现
可共享的具体享元角色
享元模式下,用的比较多的是可以共享的具体享元角色。这里我们通过享元模式来实现各种颜色的几何形状为例。这里的几何形状有两个属性:颜色、类型。类型的变化相对而言比较有限,比如正方形、圆形等。而颜色则显得变幻无穷。所以这里,我们认为对于几何形状而言,类型属于内部状态,而颜色则属于外部状态
现在,让我们先定义一个抽象享元角色ShapeFlyweight类
1 | /** |
然后再来实现一个具体享元角色Shape类,可以看到其一方面支持通过构造器实现内部状态类型的注入,另一方面亦提供了外部状态颜色的设置方法以供client使用
1 | /** |
现在就通过享元工厂ShapeFlyweightFactory来获取具体的享元角色了,可以看到工厂内部通过Map来缓存各种不同类型的具体享元角色Shape实例,实现对同一种形状类型实例的共享
1 | /** |
现在client就可以通过工厂来获取各种类型的几何形状了,而对于外部状态则需要client在每次使用前按需设定
1 | /** |
从测试结果我们也可以看到,从工厂中多次获得的某类型的几何图形实际上是同一个对象。即具体享元角色Shape的实例是可以被共享的
不可共享的具体享元角色
如果client需要同一种颜色下的多种类型的形状,如果直接使用上面Shape的类虽然也可以,但是需要client多次调用比较繁琐。为此就可以引入一个复合享元角色Shapes类,其是不可共享的。可以看到该类中内部会持有一个容器shapeSet用于保存不同类型的Shape实例,并提供了add方法用于向容器进行添加操作。而在该类中设置外部状态的setExtrinsicState方法,显然需要对容器shapeSet各Shape实例均进行设置。值得一提的是,不可共享的含义指的是Shapes实例不可共享、复用,但Shapes的容器shapeSet中存放的Shape实例是可共享、复用的
1 | /** |
同样Shapes实例也是需要通过工厂来获取的,而不允许client直接构造。故我们继续在ShapeFlyweightFactory类添加相应的工厂方法
1 | /** |
至此,client如果期望一次性拿到多种类型的几何形状,就可以通过Shapes实例很方便的实现了
1 | /** |
测试结果如下,符合预期
最后补充说明下,该设计模型在实际开发中,一般应用的并不多。其使得系统变得较为复杂。在区分对象属性是内部状态还是外部状态的过程中,有时候并不容易。该设计模式比较典型的实践有Java的String类
参考文献
- Head First 设计模式 弗里曼著