引入
书接上回,我们来聊聊什么是享元模式?
运用共享技术有效地支持大量细粒度的对象。
讲的明白点创建一堆外部不允许修改的小对象供外部程序使用,如果有很多对象用同一种参数属性,你不能copy出几百个副本让对象使用吧~,第一这会使内存消耗变大第二这本身就显得很蠢,当你要修改时要同时对几百个不同的副本进行操作。
适合享元模式的场景
- 一个应用程序使用大量的对象,这些对象之间部分属性本质上是一样的(用享元来封装相同的部分,这个很像状态模式的共享做法)
- 对象的多数状态都可变为外部状态,就可以考虑将这样的对象作为系统中的享元来使用(就是说这个对象是给外部程序调用的,就考虑加上一个享元接口,把这个对象变成一个享元)
享元模式的结构
是不是很疑惑为什么没有享元对象?
为什么在书上又写了具体享元对象呢?其实具体的享元对象是作为享元工厂的内部类存在的,我这里为了表示这个关系故意没有画出这个实现,具体享元的实现细节全部被享元工厂隐藏起来了·,这样保证了享元对象与所处的环境无关,用户程序不允许直接使用具体享元,创建和管理享元对象全部由享元工厂负责。
这样做的好处就是让具体享元和环境完全解耦,从而使具体享元可以用到任何环境中,不管调用它的是汽车还是火车,只要它还有长宽高的共享数据就可以用这个具体享元。
具体享元对象是享元工厂对象的内部对象,它是享元接口的实例对象,下面就是它的完整画法。
这个代表内部类的意思,享元工厂还采用了单例模式,所以这里的享元工厂还有一个自调用的符号!!
享元接口
public interface Flyweight {
public double getHeight(); //返回内部数据
public double getWidth();
public double getlength();
public void printMess(String mess); // 使用参数mess获取外部数据
}
享元工厂及其内部类具体享元对象
import java.util.HashMap;
//享元工厂,负责创建和管理享元对象
//它将创建享元对象的具体享元类作为自己的内部类
//而它自身则是采用单例模式确保只有它一个实例可以处理享元,进一步确保了享元的唯一性
public class FlyweightFactory {
private HashMap<String,Flyweight> hashMap;//用hashmap来存放具体享元
static FlyweightFactory factory = new FlyweightFactory(); //工厂对象实例是静态的
private FlyweightFactory(){
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,length = 0;
String[] str =key.split("#");
width = Double.parseDouble(str[0]);
height = Double.parseDouble(str[1]);
length= Double.parseDouble(str[2]);
Flyweight ft = new ConcreFlyweight(width,height,length);
hashMap.put(key,ft);
return ft;
}
}
//享元对象是享元工厂对象的内部类,这样做可以防止外部程序修改这个对象
private class ConcreFlyweight implements Flyweight {
private double width;
private double height;
private double length;
public ConcreFlyweight(double width, double height, double length) {
this.height = height;
this.width = width;
this.length = length;
}
@Override
public double getHeight() {
return height;
}
@Override
public double getWidth() {
return width;
}
@Override
public double getlength() {
return length;
}
//主动打印从外部来的数据
@Override
public void printMess(String mess) {
System.out.println(mess);
System.out.println("宽度为"+width);
System.out.println("高度为"+height);
System.out.println("长度为"+length);
}
}
}
调用享元对象的car对象
//外部程序引用享元对象
public class Car {
Flyweight flyweight; // 存放享元对象的引用
String name , color ;
int power ;
Car( Flyweight flyweight , String name , String color , int power){
this.flyweight = flyweight;
this.color = color;
this.name = name;
this.power = power;
}
public void printCar(){
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());
}
}
应用程序入口
public class Application {
public static void main(String[] args) {
//这个享元工厂用的饿汉模式,应用程序中是调用这个getFactory接口获得实例对象的
FlyweightFactory factory = FlyweightFactory.getFactory();
double width = 2,height = 2 ,length = 5 ;
//key值在工厂那里按照 #号将关键词分离了,这样的好处在于添加新的享元,我们的接口不需要修改
String key =""+width +"#"+height+"#"+length;
Flyweight flyweight = factory.getFlyweight(key);// 把享元的数据给到工厂
//下面两个获取的是享元中的内部数据,是共享的
Car A6One = new Car(flyweight,"奥迪A6","黑色",128);
Car A6Two = new Car(flyweight,"奥迪A6","灰色",160);
A6One.printCar();
A6Two.printCar();
//要是你调用printMess方法,外部数据就不是共享的,它不是享元对象的一部分
}
}
使用享元模式的优点
- 使用享元可以节省内存的开销,特别适合处理大量细粒度对象,这些对象的许多属性值是相同的,而且一旦创建不允许修改(一个经典的粒子就是这个汽车的例子)
- 享元模式中的享元可以使用方法的参数接受外部状态中的数据,但外部状态数据不会干扰到享元中的内部数据(这样享元可以在不同的环境中被共享)
总结
享元模式就和我们状态模式将状态抽离出来是一样的道理,既然很多对象都会用到同一个属性,把它抽离出来何乐而不为呢~~!