亨元模式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值