享元模式是对象的结构模式,以共享的方式高效地支持大量的细粒度对象。
共享的关键是区分内蕴状态和外蕴状态。
内蕴状态:存储在享元对象内部的,并且是不会随环境改变而有所不同的。一个享元可以具有内蕴状态并可以共享。
外蕴状态:随环境改变而改变、不可共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。
单纯享元模式的结构
在单纯享元模式中,所有的享元对象都是可以共享的。简单实现的结构如下图所示。
- 抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态的操作可以通过调用商业方法以参数形式传入。
- 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。
- 享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
- 客户端(Client)角色:本角色需要维护一个对所有享元对象的引用。本角色需要自行存储所有享元对象的外蕴状态。
abstract public class Flyweight {
// 一个示意性的方法,参数state是外蕴状态
abstract public void operation(String state);
}
具体享元类中intrinsicState为内蕴状态,在享元对象被创建时赋值,之后不会再改变;外蕴状态为operation方法的参量state,由外部传入。
public class ConcreteFlyweight extends Flyweight {
private Character intrinsicState = null;
/**
* 构造子,内蕴状态作为参量传入
*/
public ConcreteFlyweight(Character state) {
this.intrinsicState = state;
}
/**
* 外蕴状态作为参量传入方法中,改变方法的行为,
* 但是并不改变对象的内蕴状态
*/
public void operation(String state) {
System.out.print("\nIntrinsic State = " + intrinsicState + ", Extrinsic State = " + state);
}
}
客户端不可以直接将具体享元类实例化,而必须通过一个工厂对象,利用factory方法得到享元对象。一般而言,享元工厂对象在整个系统中只有一个,因此可以使用单例模式。
当客户端需要单纯享元对象的时候,需要调用享元工厂的factory方法,并传入所需的单纯享元对象的内蕴状态,由工厂方法产生所需的享元对象。
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
public class FlyweightFactory {
private HashMap files = new HashMap();
private Flyweight lnkFlyweight;
/**
* 默认构造子
*/
public FlyweightFactory() {}
/**
* 构造子,内蕴状态作为参量传入
*/
public Flyweight factory(Character state) {
if (files.containsKey(state)) {
return (Flyweight) files.get(state);
} else {
Flyweight fly = new ConcreteFlyweight(state);
files.put(state, fly);
return fly;
}
}
/**
* 辅助方法
*/
public void checkFlyweight() {
Flyweight fly;
int i = 0;
System.out.println("\n=====checkFlyweight()=====");
for (Iterator it = files.entrySet().iterator(); it.hasNext(); ) {
Map.Entry e = (Map.Entry) it.next();
System.out.println("Item " + (++i) + ": " + e.getKey());
}
System.out.println("=====checkFlyweight()=====");
}
}
public class Client {
public static void main(String[] args) {
// 创建一个享元工厂对象
FlyweightFactory factory = new FlyweightFactory();
// 向享元工厂对象请求一个内蕴状态为'a'的享元对象
Flyweight fly = factory.factory(new Character('a'));
// 以参量方式传入一个外蕴状态
fly.operation("First Call");
// 向享元工厂对象请求一个内蕴状态为'b'的享元对象
fly = factory.factory(new Character('b'));
// 以参量方式传入一个外蕴状态
fly.operation("Second Call");
// 向享元工厂对象请求一个内蕴状态为'a'的享元对象
fly = factory.factory(new Character('a'));
// 以参量方式传入一个外蕴状态
fly.operation("Third Call");
factory.checkFlyweight();
}
}
虽然申请了三个享元对象,但是实际上创建的享元对象只有两个,这就是共享的含义。
运行结果:
Intrinsic State = a, Extrinsic State = First Call
Intrinsic State = b, Extrinsic State = Second Call
Intrinsic State = a, Extrinsic State = Third Call
=====checkFlyweight()=====
Item 1: b
Item 2: a
=====checkFlyweight()=====
复合享元模式的结构
将一些单纯享元使用合成模式加以复合,形成复合享元对象。这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
- 抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态的操作可以通过调用商业方法以参数形式传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是共享的。
- 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。
- 复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。
- 享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
- 客户端(Client)角色:本角色需要自行存储所有享元对象的外蕴状态。
abstract public class Flyweight {
/**
* 外蕴状态作为参量传入到方法中
*/
abstract public void operation(String state);
}
具体享元角色的主要责任有两个:
- 实现了抽象享元角色所声明的接口operation方法,此方法接收一个外蕴状态作为参量。
- 为内蕴状态instrinsicState提供存储空间。
public class ConcreteFlyweight extends Flyweight {
private Character intrinsicState = null;
/**
* 构造子,内蕴状态作为参量传入
*/
public ConcreteFlyweight(Character state) {
this.intrinsicState = state;
}
/**
* 外蕴状态作为参量传入方法中
*/
public void operation(String state) {
System.out.print("\nIntrinsic State = " + intrinsicState + ", Extrinsic State = " + state);
}
}
具体复合享元角色的两个责任:
- 复合享元对象是由单纯享元对象通过复合而成,因此提供了add聚集管理方法。由于一个复合享元对象具有不同的聚集元素,这些聚集元素在复合享元对象被创建之后加入,这本身就意味着复合享元对象的状态是会改变的,因此复合享元对象是不能共享的。
- 复合享元角色实现了抽象享元角色所规定的接口,operation方法。这个方法有一个参量,代表复合享元对象的外蕴状态。一个复合享元的所有单纯享元对象元素的外蕴状态都是与复合享元对象的外蕴状态相等;而一个复合享元对象所含有的单纯享元对象的内蕴状态一般是不相等的,不然就没有使用价值了。
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
public class ConcreteCompositeFlyweight extends Flyweight {
private HashMap files = new HashMap(10);
private Flyweight flyweight;
/**
* 默认构造子
*/
public ConcreteCompositeFlyweight() {}
/**
* 增加一个新的单纯享元对象到聚集中
*/
public void add(Character key, Flyweight fly) {
files.put(key, fly);
}
/**
* 外蕴状态作为参量传入方法中
*/
public void operation(String extrinsicState) {
Flyweight fly = null;
for (Iterator it = files.entrySet().iterator(); it.hasNext(); ) {
Map.Entry e = (Map.Entry) it.next();
fly = (Flyweight) e.getValue();
fly.operation(extrinsicState);
}
}
}
享元工厂在多态性的基础之上提供两种不同的方法,一个用于提供单纯享元对象,另一个用于提供复合享元对象。
当客户端需要单纯享元对象的时候,需要调用享元工厂的factory方法,并传入所需的单纯享元对象的内蕴状态。
当客户端需要复合享元对象的时候,需要调用享元工厂的factory方法,并传入所需的复合享元对象的所有复合元素(也就是单纯享元对象)的内蕴状态。
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
public class FlyweightFactory {
private HashMap files = new HashMap();
/**
* 默认构造子
*/
public FlyweightFactory() {}
/**
* 复合享元工厂方法,所需状态以参量形式传入
* 这个参量恰好可以使用String类型。
* 读者完全可以使用一个聚集,如Vector对象等。
*/
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 (files.containsKey(state)) {
// 具有此状态的享元已经存在,因此直接将它返还
return (Flyweight) files.get(state);
} else {
// 具有此状态的享元不存在,因此创建新实例
Flyweight fly = new ConcreteFlyweight(state);
// 将实例存储到聚集中
files.put(state, fly);
// 将实例返还
return fly;
}
}
/**
* 辅助方法
*/
public void checkFlyweight() {
Flyweight fly;
int i = 0;
System.out.println("\n=====checkFlyweight()=====");
for (Iterator it = files.entrySet().iterator(); it.hasNext(); ) {
Map.Entry e = (Map.Entry) it.next();
System.out.println("Item " + (++i) + ": " + e.getKey());
}
System.out.println("=====checkFlyweight()=====");
}
}
public class Client {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight fly = factory.factory("aba");
fly.operation("Composite Call");
factory.checkFlyweight();
}
}
运行结果:
factory(a)
factory(b)
factory(a)
Intrinsic State = b, Extrinsic State = Composite Call
Intrinsic State = a, Extrinsic State = Composite Call
=====checkFlyweight()=====
Item 1: b
Item 2: a
=====checkFlyweight()=====
使用单例模式实现享元工厂角色
如前文所述:享元工厂对象在整个系统中只有一个,所以可以将其设计成为单例模式。
单纯享元模式中的享元工厂角色
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
public class FlyweightFactorySingleton {
private HashMap files = new HashMap();
private static FlyweightFactorySingleton myself = new FlyweightFactorySingleton();
/**
* 构造子是私有的,外界无法直接实例化
*/
private FlyweightFactorySingleton() {}
/**
* 静态工厂方法,向外界提供惟一的实例
*/
public static FlyweightFactorySingleton getInstance() {
return myself;
}
/**
* 工厂方法,向外界提供含有指定内蕴状态的对象
*/
public synchronized Flyweight factory(Character state) {
// 检查具有此状态的享元是否已经存在
if (files.containsKey(state)) {
// 具有此状态的享元对象已经存在,因此直接返还此对象
return (Flyweight) files.get(state);
} else {
// 具有此状态的享元对象不存在,因此创建新对象
Flyweight fly = new ConcreteFlyweight(state);
// 将新对象存储到聚集里
files.put(state, fly);
// 将对象返还
return fly;
}
}
/**
* 辅助方法,打印所有已经创建的享元对象清单
*/
public void checkFlyweight() {
Flyweight fly;
int i = 0;
System.out.println("\n=====checkFlyweight()=====");
for (Iterator it = files.entrySet().iterator(); it.hasNext(); ) {
Map.Entry e = (Map.Entry) it.next();
System.out.println("Item " + (++i) + ": " + e.getKey());
}
System.out.println("=====checkFlyweight()=====");
}
}
public class ClientSingleton {
private static FlyweightFactorySingleton factory;
public static void main(String[] args) {
// 创建享元工厂对象
factory = FlyweightFactorySingleton.getInstance();
// 向享元工厂对象请求一个内蕴状态为'a'的享元对象
Flyweight fly = factory.factory(new Character('a'));
// 以参量方式传入外蕴状态
fly.operation("First Call");
// 向享元工厂对象请求一个内蕴状态为'b'的享元对象
fly = factory.factory(new Character('b'));
// 以参量方式传入外蕴状态
fly.operation("Second Call");
// 向享元工厂对象请求一个内蕴状态为'a'的享元对象
fly = factory.factory(new Character('a'));
// 以参量方式传入外蕴状态
fly.operation("Third Call");
// 调用享元工厂对象的辅助方法,
// 查看到底有几个享元对象被创建
factory.checkFlyweight();
}
}
运行结果:
Intrinsic State = a, Extrinsic State = First Call
Intrinsic State = b, Extrinsic State = Second Call
Intrinsic State = a, Extrinsic State = Third Call
=====checkFlyweight()=====
Item 1: b
Item 2: a
=====checkFlyweight()=====
复合享元模式中的享元工厂角色
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
public class FlyweightFactorySingleton {
private HashMap files = new HashMap();
private static FlyweightFactorySingleton myself = new FlyweightFactorySingleton();
private Flyweight lnkFlyweight;
/**
* 构造子是私有的,外界无法直接实例化
*/
private FlyweightFactorySingleton() {}
/**
* 静态工厂方法,向外界提供惟一的实例
*/
public static FlyweightFactorySingleton getInstance() {
return myself;
}
/**
* 工厂方法,向外界提供含有指定内蕴状态的复合享元对象
*/
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 (files.containsKey(state)) {
// 具有此状态的享元对象已经存在,因此直接返还此对象
return (Flyweight) files.get(state);
} else {
// 具有此状态的享元对象不存在,因此创建新对象
Flyweight fly = new ConcreteFlyweight(state);
// 将新对象存储到聚集里
files.put(state, fly);
// 将对象返还
return fly;
}
}
/**
* 辅助方法,打印所有已经创建的享元对象清单
*/
public void checkFlyweight() {
Flyweight fly;
int i = 0;
System.out.println("\n=====checkFlyweight()=====");
for (Iterator it = files.entrySet().iterator(); it.hasNext(); ) {
Map.Entry e = (Map.Entry) it.next();
System.out.println("Item " + (++i) + ": " + e.getKey());
}
System.out.println("=====checkFlyweight()=====");
}
}
public class ClientSingleton {
private static FlyweightFactorySingleton factory;
public static void main(String[] args) {
// 调用享元工厂的工厂方法得到享元工厂的实例
factory = FlyweightFactorySingleton.getInstance();
// 向享元工厂对象请求一个状态为"aba"的复合享元对象
Flyweight fly = factory.factory("aba");
// 将一个外蕴状态传入到享元对象中
fly.operation("Composite Call");
// 调用享元工厂对象的辅助方法
factory.checkFlyweight();
}
}
运行结果:
factory(a)
factory(b)
factory(a)
Intrinsic State = b, Extrinsic State = Composite Call
Intrinsic State = a, Extrinsic State = Composite Call
=====checkFlyweight()=====
Item 1: b
Item 2: a
=====checkFlyweight()=====
享元模式应当在什么情况下使用
当以下所有的条件都满足时,可以考虑使用享元模式:
- 一个系统有大量的对象。
- 这些对象耗费大量的内存。
- 这些对象的状态中的大部分都可以外部化。
- 这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
- 软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
享元模式的优缺点
优点:
- 大幅度地降低内存中对象的数量。
缺点:
- 使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
- 将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。