`
guafei
  • 浏览: 328209 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

亨元模式

阅读更多
翻译为亨元模式,或直译为轻量级模式。所谓亨元,就是被其它对象共享使用的
对象。通过这种方式,系统减少了内存的占用。比如一个系统有一个成百上千、
成千上万的小对象,这些小对象可能拥有着共同的信息,那么这些共同的信息就
没有必要也重复成千上万次。把这些共同信息抽取出来,形成一个对象,即是亨
元。这些成千上万的其它对象只需要引用这个亨元即可。

举个例子,在棋类程序中,有时候我们会把一个棋子当成为一个对象。这个对象
包含着位置信息、字体信息、颜色信息、样式信息等。如下所示,

class 棋子{
  public 名字(车、马、炮等)
  public 位置信息
  public 字体信息
  public 颜色信息
  public 样式信息
}

如果我们每次new一个这样的对象,它所占用的内存是这些信息所占内存的和。
new 10个这样的对象就需要10倍的内存占用。实际上,在象棋中,除了象棋棋子
的名字不同、位置不同外,其它的字体信息、样式信息一般都是一样的。比如红
方的棋子是红色、隶书、字体有阴影效果,黑方的棋子是黑色、隶书、有阴影效
果。

这些字体信息、颜色信息、样式信息都可以做成亨元。拿颜色信息来说,如果不
使用亨元,则每一个棋子都要分别创建一个颜色对象,10个棋子就创建10个颜色
对象,而这10个颜色对象很可能是一样的,比如都是红色。这样就太浪费内存了。
使用亨元,则可以避免这种重复创建相同对象的问题。

我们只需要创建一个红色信息的对象,而让所有红色棋子都引用这个红色对象。
同理,让所有黑色棋子都引用黑色对象。这样就减少了内存使用。

比如,我们在new 一个红色棋子对象时,要先去一个颜色信息池中区查找是否已
经有这么一个红色对象,有则,直接引用它,没有则先创建它再引用它。所谓颜
色信息池,一般都用Map来实现。比如

Map h = new HashMap();
if(h.get("red")!=null){
    h.pub("red",new Color对象);
}else{
    取出这个Color对象(我们叫它objectC)。
}

然后你在另一个地方把这个objectC赋给棋子对象,比如用这种方式:
棋子对象.颜色信息=objectC;

实际上,针对象棋这个例子也没有必要用一个pool来保存Color对象,也没有必要
判断这个对象是不是在pool中存在,也没必要在判断完是否存在后再进行是否生
成一个新对象的工作。因为象棋中无非两种对象,红和黑,这是确定的。所以我
们完全可以把这两个对象提前生成出来,放到一个地方保存着,别人都对它们进
行引用即可,可以这样做,

class 棋子{
    private static Color redColor = new Color("red");
    private static Color blackColor = new Color("black");
...
...     
}

这好像有点单态模式的感觉。实际上,一个class会共享很多实例而不像单态那样
只允许共享一个实例,也不像象棋这样只有红、黑两种颜色对象。假如一个应用
中,class共享的颜色对象可能除了红、黑,还有其它上百种颜色,而且颜色种类
的增减,依赖于用户的操作,那么用一个pool把它们存放起来就比较好。

一般亨元的获取都是通过一个工厂类来实现,例子如下:

class A{
    private B b;
    private C c;
    private Flyweight f;
    public A(B b,C c,Flyweight f){
        this.b = b;
        this.c = c;
        this.f = f;
    }
}

class Factory{
    private static Factory f = new Factory();
    private static Map m = new HashMap();
    private Factory(){};
    public Factory getInstance(){return f;}
    public void getFlyweight(Object key){
        Flyweight fly;
        if(m.get(key)==null){
             fly = new Flyweight(key);
             f.put(key,fly);
        }else{
             fly = (Flyweight)f.get(key);
        }
        return fly;
     }
}

class Client{
    public static void main(String args[]){
        B b = new B();
        C c = new C();
        Flyweight f = Factory.getInstance().getFlyweight(key);
        A a = new A(b,c,f);
    }
}

下面3个类的具体内容不重要,略
class B
class C
class Flyweight

当用户new一个A的实例,A中的Flyweight对象只是指向共享对象的一个引用。在
这个例子中,使用HashMap来保存共享对象,随着程序的运行,HashMap存的对象
会越来越多,HashMap自己会变的越来越大。有的时候,因为用户的操作也可能会
使对象被杀掉(比如棋子被吃掉),以至于有的亨元没有被任何对象引用,这个
时候,如果本身已经有点臃肿的HashMap能瘦身,自动释放亨元,对系统性能的提
升也非常有好处。比较简单的一个办法就是用WeakHashMap代替HashMap.

Flyweight模式是一种Cache技术,站在更高的层面看,它是一种资源使用技术。
这种技术更多的见于对性能要求比较高的底层开发领域。Java语言中的某县内置
对象可能就是用Flyweight模式实现的。比如最常用的String对象就可能是如此,
可以做一个简单测试,如下,

public class Test {
public static void main(String[] args) {
    String a1 = "aaa"
    String b1 = "bbb";
    String a2 = "aaa";
    String b2 = "bbb";
    System.out.println(a1 == a1);
    System.out.println(b1 == b2);                                    
    String c = a + b;
    System.out.println(c == "aaabbb");
    String flyweight = (a + b).intern();//这里用到了亨元模式. 返回"aaabbb"的一个引用。
    System.out.println(flyweight == "aaabbb");
  }
}
程序分别返回 true,true,false,true.  (==用来测试两者是不是引用同一个实例).

String在java中的应用甚为频繁,所以使用Flyweight模式以提高性能理所当然,
但事实上String很多地方并未使用该模式,比如

String a =new String("aaa");
String b = new String("bbb");
System.out.println(a == b);

程序打印出false.

或许这是Java为开发者故意提供的一个让同一个字符串有多个实例的选择。

最后要注意的一点是,这些共享的对象有可能在途中被改变。你要根据不同的应
用来决定是不是允许共享对象改变,如果不允许就把它们设计成不可变对象
(immutable)。如果允许变,则要考虑是不是创建个新的亨元同时保留老亨元,或
者通知那些使用亨元的对象亨元已经改变了。根据你的具体情况具体分析具体设





版本二:

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)亨元模式将亨元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
分享到:
评论

相关推荐

    c#代码介绍23种设计模式-12亨元模式(附代码)

    这样可能会出现内存使用越来越多的情况,这样的问题是非常严重,然而享元模式可以解决这个问题 1. 享元模式的实现精髓: “既然都是同一个对象,能不能只创建一个对象,然后下次需要创建这个对象的时候,让它直接用...

    Java24种设计模式,Java24种设计模式,24种设计模式,学会了这24种设计模式,可以打遍天下无敌手,设计模式非常重要

    1、策略模式STRATEGY PATTERN 2、代理模式PROXY PATTERN 3、单例模式SINGLETON PATTERN 4、多例模式MULTITION PATTERN 5、工厂方法模式FACTORY METHOD PATTERN 6、抽象工厂模式ABSTRACT ...23、亨元模式 24、备忘录模式

    您的设计模式

    亨元模式使用共享对象可有效地支持大量的细粒度的对象。 24. 备忘录模式(Memento Pattern) 备忘录模式在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到...

    常见的23个设计模式详解

    亨元模式运用共享技术来有效地支持大量细粒度的对象。它通过共享相同数据的方式来减少内存消耗。这种模式适用于对象的外部状态和内部状态可以分离,且对象的外部状态可以通过传入参数来确定。 ### 24. 备忘录模式...

    Java设计模式经典搞笑珍藏版

    亨元模式运用共享技术有效地支持大量细粒度的对象。 #### 二十五、备忘录模式(Memento Pattern) 备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该...

    24种设计模式介绍与6大设计原则

    16. 亨元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象。 17. 享元模式(享元模式在文档中出现了两次,这里默认为漏识别,实际上享元模式是设计模式中的一种)。 18. 备忘录模式(Memento ...

    设计模式(经典讲解设计模式)

    - **亨元模式**(Flyweight Pattern) - **备忘录模式**(Memento Pattern) 每一种设计模式都有其特定的应用场景和优势,理解并正确应用这些模式能够帮助开发人员构建更加健壮和灵活的软件系统。

    java24种设计模式.pdf

    - 亨元模式(FlyweightPattern):运用共享技术有效地支持大量细粒度的对象。 - 备忘录模式(MementoPattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将...

    23中设计模式

    23. 亨元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象。 24. 备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将...

    Java23种设计模式讲解

    23. 亨元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象。 除了上述23种设计模式外,书中还讲解了六大设计原则,它们是面向对象设计的基本原则,可用来提高代码的可复用性、灵活性和可维护性...

    java设计模式之禅

    亨元模式(FlyweightPattern)是一种结构型设计模式,主要用于减少创建对象的数量,以减少内存占用和提高性能。这种模式尝试重用现有的同类对象,如果未找到,则创建新对象。 备忘录模式(MementoPattern)是一种...

    开发设计模式

    23. 亨元模式【FlyweightPattern】:运用共享技术有效地支持大量细粒度的对象。 24. 备忘录模式【MementoPattern】:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该...

    设计模式王维雄实用教案.pptx

    6. 亨元模式:通过共享技术,有效地支持大量细粒度对象。 7. 代理模式:为其他对象提供一个代理以控制对该对象的访问。 行为型模式涉及对象之间的交互和职责分配,例如: 1. 命令模式:将请求封装为一个对象,以便...

    C语言设计模式.docx

    19. **亨元模式**:运用共享技术有效地支持大量细粒度的对象。在C语言中,通过共享内存和引用计数来实现。 20. **代理模式**:为其他对象提供一种代理以控制对这个对象的访问。C语言中可以使用函数指针或间接访问...

Global site tag (gtag.js) - Google Analytics