享元模式
运用共享技术有效的支持大量细粒度的对象。
Flyweight Pattern
Use sharing to support large numbers of fine-grained objects efficiently.
类图

模式的结构与使用
享元方法模式的结构中包括三种角色。
+ 享元接口(Flyweight):是一个接口,该接口定义了享元对外公开其内部数据的方法,以及享元接收外部数据的方法。
+ 具体享元(Concrete Flyweight):实现享元接口的类,该类的实例称为享元对象,或简称享元。具体享元类的成员变量为享元对象的内部状态,享元对象的内部状态必须与所处的周围环境无关,即要保证使用享元对象的应用程序无法更改享元的内部状态,只有这样才能使得享元对象在系统中被共享。因为享元对象是用来共享的,所以不能允许用户各自地使用具体享元类来创建对象,这样就无法达到共享的目的,因为不同用户用具体享元类创建的对象显然是不同的,所以,具体享元类的构造方法必须是private的,其目的是不允许用户程序直接使用具体享元类来创建享元对象,创建和管理享元对象由享元工厂负责。
+ 享元工厂(Flyweight Factory):享元工厂是一个类,该类的实例负责创建和管理享元对象,用户或其他对象必须请求享元工厂为它得到一个享元对象。享元工厂可以通过一个散列表(也称共享池)来管理享元对象,当用户程序或其他若干个对象向享元工厂请求一个享元对象时,如果享元工厂的散列表中已有这样的享元对象,享元工厂就提供这个享元对象给请求者,否则就创建一个享元对象添加到散列表中,同时该享元对象提供给请求者。显然,当若干个用户或对象请求享元工厂提供一个享元对象时,第一个用户获得该享元对象的时间可能慢一些,但是后继的用户会较快地获得这个享元对象。可以使用单例模式来设计享元工厂,即让系统中只有一个享元工厂的实例。另外,为了让享元工厂能生成享元对象,需要将具体享元类作为享元工厂的内部类。
简单的例子
Flyweight的抽象类Flyweight.java
package Flyweight;
public interface Flyweight {
public double getHeight(); //返回内部数据
public double getWidth();
public double getLength();
public void printMess(String mess); //使用参数mess获取外部数据
}
Flyweight Factory的实现类FlyweightFactory.java
package Flyweight;
import java.util.HashMap;
public class FlyweightFactory {
private HashMap<String, Flyweight> hashMap;
static FlyweightFactory factory = new FlyweightFactory();
public FlyweightFactory() {
this.hashMap = new HashMap<String, Flyweight>();
}
public static FlyweightFactory getFactory() {
return factory;
}
public synchronized Flyweight getFlyweight(String key) {
if (hashMap.containsKey(key)) {
return hashMap.get(key);
} else {
double width = 0,height = 0,lenght = 0;
String[] str = key.split("#");
width = Double.parseDouble(str[0]);
height = Double.parseDouble(str[1]);
lenght = Double.parseDouble(str[2]);
Flyweight ft = new ConcreteFlyweight(width, height, lenght);
hashMap.put(key, ft);
return ft;
}
}
class ConcreteFlyweight implements Flyweight {
private double width;
private double height;
private double lenght;
public ConcreteFlyweight(double width, double height, double lenght) {
this.width = width;
this.height = height;
this.lenght = lenght;
}
@Override
public double getHeight() {
return height;
}
@Override
public double getWidth() {
return width;
}
@Override
public double getLength() {
return lenght;
}
@Override
public void printMess(String mess) {
System.out.println(mess);
System.out.print("宽度:" + width);
System.out.print("高度:" + height);
System.out.print("长度:" + lenght);
}
}
}
需要使用共享数据的类Car.java
package Flyweight;
public class Car {
Flyweight flyweight;
String name, color;
int power;
public Car(Flyweight flyweight, String name, String color, int power) {
this.flyweight = flyweight;
this.name = name;
this.color = color;
this.power = power;
}
public void print() {
System.out.println("名称:" + name);
System.out.println("颜色:" + color);
System.out.println("功率:" + power);
System.out.println("宽度:" + flyweight.getWidth());
System.out.println("高度:" + flyweight.getHeight());
System.out.println("长度:" + flyweight.getLength());
}
}
测试类Application.java
package Flyweight;
public class Application {
public static void main(String[] args) {
FlyweightFactory factory = FlyweightFactory.getFactory();
double width = 1.82, height = 1.47, lenght = 5.12;
String key = "" + width + "#" + height + "#" + lenght;
Flyweight flyweight = factory.getFlyweight(key);
Car audiA60ne = new Car(flyweight, "奥迪A6", "黑色", 128);
Car audiA6Two = new Car(flyweight, "奥迪A6", "灰色", 160);
//audiA60ne和audiA6Two没有向享元传递外部数据,而是获取享元的内部数据
audiA60ne.print();
audiA6Two.print();
width = 1.77;
height = 1.45;
lenght = 4.63;
key = "" + width + "#" + height + "#" + lenght;
Car audiA40ne = new Car(flyweight, "奥迪A4", "蓝色", 126);
Car audiA4Two = new Car(flyweight, "奥迪A4", "红色", 138);
//audiA40ne和audiA4Two向享元传递外部数据,这些数据是不共享的
flyweight.printMess("名称: 奥迪A4 颜色:蓝色 功率:126");
flyweight.printMess("名称: 奥迪A4 颜色:红色 功率:138");
}
}
执行结果

享元模式的优点
- 使用享元模式可以节省内存的开销,特别适合处理大量细粒度对象,这些对象的许多属性值是相同的,而且一旦创建则不容许修改。
- 享元模式中的享元可以使用方法的参数接收外部状态中的数据,但外部状态数据不会干扰到享元中的内部数据,这就使享元可以在不同的环境中被共享。
适用享元模式的情景
下列情况之一就可以考虑使用享元模式:
- 一个应用程序使用大量的对象,这些对象之间部分属性本质上是相同的,这时应使用享元来封装相同的部分。
- 对象的多数状态都可变为外部状态,就可以考虑将这些对象作为系统中的享元来使用。