运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种 对象结构型模式。
在享元模式中可以共享的相同内容称为 内部状态(Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为 外部状态(Extrinsic State),其中外部状态和内部状态是相互独立的,外部状态的变化不会引起内部状态的变化。由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。也就是说,享元模式的本质是分离与共享 : 分离变与不变,并且共享不变。把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)(用于存储具有相同内部状态的享元对象)。在享元模式中,共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为 细粒度对象。
享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
Flyweight: 享元接口,通过这个接口传入外部状态并作用于外部状态;
ConcreteFlyweight: 具体的享元实现对象,必须是可共享的,需要封装享元对象的内部状态;
UnsharedConcreteFlyweight: 非共享的享元实现对象,并不是所有的享元对象都可以共享,非共享的享元对象通常是享元对象的组合对象;
FlyweightFactory: 享元工厂,主要用来创建并管理共享的享元对象,并对外提供访问共享享元的接口;
3.1单纯享元模式
在单纯享元模式中,所有的享元对象都是可以共享的,即所有抽象享元类的子类都可共享,不存在非共享具体享元类。
首先,我们创建享元接口Flyweight:
/**
* 抽象享元角色
*/
public interface Flyweight {
//一个示意性方法,参数state是外蕴状态
public void operation(String state);
}
然后,我们实现享元接口,实现类也是享元对象,
/**
* 具体享元角色类,有一个内蕴状态intrinsicState为成员属性,它的值应该在享元对象被创建时赋予,
* 所有的内蕴状态在对象创建后,就不会再改变。如果享元对象有外蕴的话,都是储存在客户端的。
*/
public class ConcreteFlyweight implements Flyweight{
private Character intrinsicState=null;
/**
* 构造函数,内蕴状态作为参数传入,在对象初始化时传入
* @param state
*/
public ConcreteFlyweight(Character state){
this.intrinsicState = state;
}
//外蕴状态,不改变内蕴装态
@Override
public void operation(String state) {
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
然后,我们定义工厂类,工厂类根据内蕴状态,产出享元对象
/**
* 享元工厂角色,也可以使用单例模式实现此角色。客户端传入所需共享对象的内蕴状态,来获得享元对象。
*/
public class FlyweightFactory {
//key为内蕴状态,value为享元对象,files存储享元对象。当需要享元对象时,传入key值,获得对象。
//当对象不存在时,新建对象,并存入files中,这样,再有人用这个对象时,直接取即可。这就是共享。
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
//获取享元对象
public Flyweight factory(Character state){
//先从缓存中查找对象
Flyweight fly = files.get(state);
if(fly == null){
//如果对象不存在则创建一个新的Flyweight对象
fly = new ConcreteFlyweight(state);
//把这个新的Flyweight对象添加到缓存中
files.put(state, fly);
}
return fly;
}
}
这样,就实现了共享。我们可以看到,对于内蕴状态而言,就是共享一个的,而对于外蕴状态而言,客户端是可以自己定义的。
3.2复合享元模式
将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
在复合模式中,增加了一个复合实现类,在这个实现类里,要提供聚集方法,来聚集单纯享元对象。
/**
* 符合模式享元角色类,由单纯的享元对象复合而成,复合享元对象是不能被共享的
*/
public class ConcreteCompositeFlyweight implements Flyweight{
//将单纯享元对象聚集到Map集合中
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
/**
* 增加一个新的单纯享元对象到聚集中
*/
public void add(Character key , Flyweight fly){
files.put(key,fly);
}
/**
* 外蕴状态作为参数传入到方法中
*/
@Override
public void operation(String state) {
Flyweight fly = null;
for(Object o : files.keySet()){
fly = files.get(o);
fly.operation(state);
}
}
}
在享元工厂中,也相应的需要增加产出复合享元对象的方法,代码如下:
```java
public class FlyweightFactory {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
/**
* 复合享元工厂方法
*/
public Flyweight factory(List<Character> compositeState){
ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight();
for(Character state : compositeState){
compositeFly.add(state,this.factory(state));
}
return compositeFly;
}
/**
* 单纯享元工厂方法
*/
public Flyweight factory(Character state){
//先从缓存中查找对象
Flyweight fly = files.get(state);
if(fly == null){
//如果对象不存在则创建一个新的Flyweight对象
fly = new ConcreteFlyweight(state);
//把这个新的Flyweight对象添加到缓存中
files.put(state, fly);
}
return fly;
}
}
这样,我们可以获得复合享元对象,并从复合享元对象中获取单纯享元对象。
总结:
上面的代码实现,只是为了体现享元模式的思想,在许多源码的实现中,并并不是按照上面的套路来实现享元模式的。所以,在学习设计模式时,我们最终掌握的,还是设计模式的思想。
优点:
(1)它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份;
(2)享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点:
(1)享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化;
(2)为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
各种池技术都应用了享元模式,如JDK中的String常量池,数据库连接池等等,都是享元模式思想的应用。