简介
面向对象的思想很好地解决了抽象性的问题,一般也不会出现性能上的问题。但是在某些情况下,对象的数量可能会太多,从而导致了运行时的代价。那么我们如何去避免大量细粒度的对象,同时又不影响客户程序使用面向对象的方式进行操作。
图1 Flyweight模式结构图
例子
比如围棋有300颗棋子,
用一般的设计模式,
创建一个类,
每个棋子都用一个对象的话
那就会非常麻烦,并且各自定义各自在棋盘的位置.....等等
而使用 亨元模式 来实现的话,
就用两个对象 :一个黑,一个白。
这样就可以了,至于棋子的方位不同,那只是对象的不同的外部表现形式或者说是外部状态。
这样三百多个对象就减到了两个对象。
享元模式以共享的方式高效地支持大量的细粒度对象,
说的再具体一些是将所有具有相同状态的对象指向同一个引用,
从而解决了系统在创建大量对象时所带来的内存压力。
享元模式应用较少,这里再举一个森林和树木的例子来说明这个模式的应用。
一片森林中有成千上万棵树木,如果每棵树都创建一个对象,那么内存中的对象数量相当庞大,更何况我们现实世界中还有成千上万个森林。
亨元(Flyweight Pattern)模式
1、亨元模式的用意
亨元模式是对象的结构模式。亨元模式以共享的方式高效地支持大量的细粒度对象。
亨元模式能做到共享的关键是区分内蕴状态和外蕴状态
一个内蕴状态是存储在亨元对象内部的,并且是不会随环境改变而有所不同的。因此,一个亨元可以具有内蕴状态并可以共享。
一个外蕴状态是随环境改变而改变的、不可以共享的状态。亨元对象的外蕴状态必须由客户端保存,并在亨元对象被创建之后,
在需要使用的时候再传入到亨元对象内部。
外蕴状态不可以影响亨元对象的内蕴状态的,它们是相互独立的。
2、亨元模式的种类
根据所涉及的亨元对象的北部表象,亨元模式可以分为单纯亨元模式和复合亨元模式两种形式。
3、亨元模式的实现:
1)单纯亨元模式涉及的角色
1-抽象亨元角色:此角色是所有的具体亨元类的超类,为这些规定出需要实现的公共接口,那些需要外蕴状态的操作
可以通过方法的参数传入。抽象亨元的接口使得亨元变得可能,但是并不强制子类实行共享,因此并非所有的亨元
对象都是可以共享的
2-具体亨元角色:实现抽象亨元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
亨元对象的内蕴状态必须与对象所处的周围环境无关,从而使得亨元对象可以在系统内共享。有时候具体亨元角色
又叫做单纯具体亨元角色,因为复合亨元角色是由单纯具体亨元角色通过复合而成的
3-复合亨元角色:复合亨元角色所代表的对象是不可以共享的,但是一个复合亨元对象可以分解成为多个本身是单纯亨元
对象的组合。复合亨元角色又称做不可共享的亨元对象。
4-亨元工厂角色:本角色负责创建和管理亨元角色。本角色必须保证亨元对象可以被系统适当地共享。
当一个客户端对象请求一个亨元对象的时候,亨元工厂角色需要检查系统中是否已经有一个符合要求的亨元对象,
如果已经有了,亨元工厂角色就应当提供这个已有的亨元对象;如果系统中没有一个适当的亨元对象的话,
亨元工厂角色就应当创建一个新的合适的亨元对象。
5-客户端角色:本角色还需要自行存储所有亨元对象的外蕴状态。
- //抽象亨元角色
- public abstract class Flyweight{
- public abstract void operation(String state);
- }
- //具体亨元角色
- 具体亨元角色的主要责任:
- 1)实现了抽象亨元角色所声明的接口,也就是operation()方法。operation()方法 接收一个外蕴状态作为参量。
- 2)为内蕴状态提供存储空间,在本实现中就是intrinsicState属性。亨元模式本身对内蕴状态的存储类型并无要求
- 这里的内蕴状态是Character类型,是为了给符合亨元的内蕴状态选做String类型提供方便。
- public class ConcreteFlyweight extends Flyweight{
- private Character intrinsicState = null;
- public ConcreteFlyweight(Character state){
- this.intrinsicState = state;
- }
- //外蕴状态作为参量传入到方法中
- public void operation(String state){
- System.out.print("\nInternal State = " + intrinsicState + "Extrinsic State = " +
- state);
- }
- }
- //具体复合亨元角色
- 具体复合亨元角色的责任:
- 1)复合亨元对象是由单纯的亨元对象通过复合而成,因此它提供了add()这样的聚集管理方法。
- 由于一个复合亨元对象具有不同的聚集元素,这些聚集元素在复合亨元对象被创建之后加入,这本身就意味着
- 亨元对象的状态是会改变的,因此复合亨元对象是不能共享的。
- 2) 复合亨元角色实现了抽象亨元角色所规定的接口, 也就是operation()方法。这个方法有一个参量,
- 代表复合亨元对象的外蕴状态,。一个复合亨元对象的所有单纯亨元对象元素的外蕴状态都是与复合亨元对象的
- 外蕴状态相等的,而一个复合亨元对象所含有的单纯亨元对象的内蕴状态一般是不相等的,不然就没有使用价值了。
- import java.util.Map;
- import java.util.HashMap;
- import java.util.Iterator;
- public class ConcreteCompositeFlyweight extends Flyweight{
- private HashMap flies = new HashMap(10);
- private Flyweight flyweight;
- public ConcreteCompositeFlyweight(){}
- //增加一个新的单纯亨元对象到聚集中
- public void add(Character key, Flyweight fly){
- flies.put(key,fly);
- }
- //外蕴状态作为参量传入到方法中
- public void operation(String extrinsicState){
- Flyweight fly = null;
- for(Iterator it = flies.entrySet().iterator()); it.hasNext();){
- Map.Entry e = (Map.Entry)it.next();
- fly = (Flyweight)e.getValue();
- fly.operation(extrinsicState);
- }
- }
- }
- //亨元工厂角色
- import java.util.Map;
- import java.util.HashMap;
- import java.util.Iterator;
- public class FlyweightFactory{
- private HashMap flies = new HashMap();
- public FlyweightFactory(){}
- //复合亨元工厂方法,所需状态以参量形式传入,这个参量恰好可以使用String类型
- public Flyweight factory(String compositeState){
- ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight();
- int length = compositeState.length();
- Character state = null;
- for(int i = 0; i < length; i ++){
- state = new Character(compositeState.charAt(i));
- System.out.println("factory(" + state +")");
- compositeFly.add(state,this.factory(state));
- }
- return compositeFly;
- }
- //单纯亨元工厂方法
- public Flyweight factory(Character state){
- //检查具有此状态的亨元是否已经存在
- if(flies.containsKey(state)){
- //具有此状态的亨元已经存在,因此直接将它返回
- retun (Flyweight)flies.get(state);
- }else{
- //具有此状态的亨元不存在,因此创建新实例
- Flyweight fly = new ConcreteFlyweight(state);
- //将实例存储到聚集中
- flies.put(state,fly);
- //将实例返回
- return fly;
- }
- }
- public void checkFlyweight(){
- Flyweight fly;
- int i = 0;
- System.out.println("\n==========CheckFlyweight()==============");
- for(Iterator it = flies.entrySet().iterator(); it.hasNext();){
- Map.Entry e = (Map.Entry) it.next();
- System.out.println("Item" + (++i) + ";" + e.getKey());
- }
- System.out.println("\n==========CheckFlyweight()==============");
- }
- }
4、模式的实现
1)使用不变模式实现亨元角色
亨元模式里的亨元对象不一定非得是不变对象,但是很多的亨元对象确实被设计成了不变对象。
由于不变对象的状态之后就不再变化,因此不变对象满足亨元模式对亨元对象的要求。
2)使用备忘录模式实现亨元工厂角色
亨元工厂负责维护一个表,通过这个表把很多全同的实例与代表它们的一个对象联系起来。这就是备忘录模式的应用。
3)使用单例模式实现亨元工厂角色
系统往往只需要一个亨元工厂的实例,所以亨元工厂可以设计成为单例模式。
- 在单纯的共享模式中使用单例模式实现共享工厂角色
- import java.util.Map;
- import java.util.HashMap;
- import java.util.Iterator;
- public class FlyweightFactorySingleton{
- private HashMap flies = new HashMap();
- private static FlyweightFactorySingleton myself = new FlyweightFactorySingleton();
- private FlyweightFactorySingleton(){}
- public static FlyweightFactorySingleton getInstance(){
- return myself;
- }
- //工厂方法,向外界提供含有指定内蕴状态的对象
- public synchronized Flyweight factory(Character state){
- //检查具有此状态的亨元是否已经存在
- if(flies.containsKey(state)){
- //具有此状态的亨元对象已经存在,因此直接返还此对象
- return (Flyweight)flies.get(state);
- }else{
- Flyweight fly = new ConcreteFlyweight(state);
- flies.put(state,fly);
- return fly
- }
- }
- //辅助方法,打印所有已经创建的亨元对象清单
- public void checkFlyweight(){
- Flyweight fly;
- int i = 0;
- System.out.println("\n===========checkFlyweight===============");
- for(Iterator it = flies.entrySet().iterator(); it.hasNext();){
- Map.Entry e = (Map.Entry)it.next();
- System.out.println("Item" + (++i) + ":" + e.getKey());
- }
- System.out.println("\n===========checkFlyweight===============");
- }
- }
- //客户端代码
- public class ClientSingleton{
- private static FlyweightFactorySingleton factory;
- public static void main(String args[]){
- factory = FlyweightFactorySingleton.getInstance();
- Flyweight fly = factory.factory(new Character('a'));
- fly.operation("First Call");
- fly = factory.factory(new Character('b'));
- fly.operation("Second call");
- Flyweight fly = factory.factory(new Character('a'));
- fly.operation("Third Call");
- factory.checkFlyweight();
- }
- }
- //将一个共享工厂角色用单例模式实现
- import java.util.Map;
- import java.util.HashMap;
- import java.util.Iterator;
- public class FlyweightFactorySingleton{
- private static FlyweightFactorySingleton myself = new FlyweightFactorySingleton();
- private HashMap flies = new HashMap();
- private Flyweight inkFlyweight;
- private FlyweightFactorySingleton(){}
- public static FlyweightFactorySingleton getInstance(){
- return new FlyweightFactorySingleton();
- }
- public Flyweight factory(String complexState){
- ConcreteCompositeFlyweight complexFly = new ConcreteCompositeFlyweight();
- int length = complexState.length();
- Character state = null;
- for(int i = 0; i < length; i ++){
- state = new Character(complexState.charAt(i));
- System.out.println("factory(" + state + ")");
- complexFly.add(state,this.factory(state));
- }
- return complexFly;
- }
- public synchronized Flyweight factory(Character state){
- //检查具有此状态的亨元是否已经存在
- if(flies.containsKey(state)){
- return (Flyweight)flies.get(state);
- }else{
- Flyweight fly = new ConcreteFlyweight(state);
- flies.put(state,fly);
- return fly;
- }
- }
- public void checkFlyweight(){
- Flyweight fly;
- int i = 0;
- System.out.println("\n===========checkFlyweight===============");
- for(Iterator it = flies.entrySet().iterator(); it.hasNext();){
- Map.Entry e = (Map.Entry)it.next();
- System.out.println("Item" + (++i) + ":" + e.getKey());
- }
- System.out.println("\n===========checkFlyweight===============");
- }
- }
- //一个应用亨元模式的咖啡摊例子
- //抽象亨元角色,serve()它没有参量是因为没有外蕴状态
- public abstract class Order{
- //将咖啡卖客人
- public abstract void serve();
- //返还咖啡的名字
- public abstract String getFlavor();
- }
- //具体亨元角色
- public class Flavor extends Order{
- private String flavor;
- public Flavor(String flavor){
- this.flavor = flavor;
- }
- public String getFlavor(){
- return this.flavor;
- }
- //将咖啡卖给客人
- public void serve(){
- System.out.println(System.out.println("Serving flavor " + flavor);
- }
- }
- //工厂角色
- public class FlavorFactory{
- private Order[] flavors = new Flavor[10];
- private int ordersMade = 0;
- private int totalFlavors = 0;
- //工厂方法,根据所需的风味提供咖啡
- public Order getOrder(String flavorToGet){
- if(ordersMade > 0){
- for(int i = 0 ; i < ordersMade; i ++){
- if(flavorToGet.equals(flavors[i].getFlavor())){
- return flavors[i];
- }
- }
- }
- flavors[ordersMade] = new Flavor(flavorToGet);
- totalFlavors++;
- return flavors[ordersMade++];
- }
- //辅助方法,返还创建过的风味对象的个数
- public int getTotalFlavorsMade(){
- return totalFlavors;
- }
- }
- //客户端代码,代表咖啡摊侍者
- public class ClientFlavor{
- private static Order[] flavors = new Flavor[20];
- private static int ordersMade = 0;
- private static FlavorFactory flavorFactory;
- //提供一杯咖啡
- private static void takeOrders(String aFlavor){
- flavors[ordersMade++] = flavorFactory.getOrder(aFlavor);
- }
- public static void main(String args[]){
- flavorFactory = new FlavorFactory();
- takeOrders("Black Coffee");
- takeOrders("Capucino");
- takeOrders("Espresso");
- takeOrders("Espresso");
- takeOrders("Capucino");
- takeOrders("Capucino");
- takeOrders("Black Coffee");
- takeOrders("Espresso");
- takeOrders("Capucino");
- //将所创建的对象卖给客人
- for(int i = 0; i < ordersMade; i ++){
- flavors[i].serve();
- }
- System.out.println("\nTotal teaFlavor objects made: " +
- flavorFactor.getTotalFlavorsMade() );
- }
- }
- //如果这个例子再大点,可以增加一个外蕴状态,也就是说可以增加桌子号,建立一个桌子类,将桌子号作为
- 参数传给serve(Table table).
5、亨元模式的使用情况
当以下所有的条件都满足时,可以考虑使用亨元模式:
(1)一个系统有大量的对象。
(2)这些对象耗费大量的内存
(3)这些对象的状态中大部分都可以外部化
(4)这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
(5)软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
应当在有足够多的亨元实例可供共享时才值得使用亨元模式。
6、怎样做到共享
一个亨元对象之所以可以被很多的用户端共享,是因为它只含有可以共享的状态,而没有不可以共享的状态,
这就是使用亨元模式的前提。
要做到符合亨元模式这一点,需要分两步走:
(1)将可以共享的状态和不可以共享的状态从此常规类中区分开来,将不可共享的状态从类里剔除出去。
那些对所有客户端都去相同的值的状态是可以共享的状态;而那些对不同的客户端有不同值的状态是不可以共享的状态
(2)这个类的创建过程必须由一个工厂对象加以控制。
这个工厂对象应当使用一个内部列表保存所有的已经创建出来的对象。当客户端请求一个新的对象时,
工厂对象首先检查列表,看是否已经有一个对象。如果已经有了,就直接返还此对象,如果没有就创建一个新对象。
亨元模式要求将可以共享的状态设置为内蕴状态,而将不可以共享的状态设置成外蕴状态,将它们外部化。
7、亨元模式的优缺点
亨元模式的优点在于它大幅度降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:
(1)亨元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化
(2)亨元模式将亨元对象的状态外部化,而读取外部状态使得运行时间稍微变长。