享元模式英文称为“Flyweight Pattern”,又译为羽量级模式或者蝇量级模式。享元模式的定义为:采用一个共享类来避免大量拥有相同内容的“小类”的开销。这种开销中最常见、直观的影响就是增加了内存的损耗。享元模式以共享的方式高效的支持大量的细粒度对象,减少其带来的开销。
事物之间都是不同的,但是又存在一定的共性,如果只有完全相同的事物才能共享,那么享元模式可以说就是不可行的;因此我们应该尽量将事物的共性共享,而又保留它的个性。为了做到这点,享元模式中区分了内蕴状态和外蕴状态。内蕴状态就是共性,外蕴状态就是个性了。
内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的;外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不同的对象出来。
JDK中的享元模式
Integer、Long、Byte、String等都用到了享元模式.
/*(1)String*/
String str1 = "test";
String str2 = "test";
System.out.println(str1 == str2); //true
/*(2)Integer*/
Integer num = 127;
Integer num2 = 127;
System.out.println(num == num2); //true
Integer num3 = 128;
Integer num4 = 128;
System.out.println(num3 == num4); //false
(1)String的常量池即用到了享元模式,所以为true。他首先检查常量池中是否有test这个值,若有则直接指向,不会重新创建String对象。
(2)Integer(Long、Byte等)内部维护着一个Cache,用于共享。(若值在-128~127之间,是可以直接从Cache中获取的。若不在这范围内,才会去new返回新对象。)
(一)两种享元模式结构
单纯享元模式的结构:
1) 抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式
通过此方法传入。在 Java 中可以由抽象类、接口来担当。
2) 具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供
存储空间。
3) 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关
键!
4) 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
来用类图来形象地表示出它们的关系吧。
复合享元模式的结构:
1) 抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式
通过此方法传入。在 Java 中可以由抽象类、接口来担当。
2) 具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供
存储空间。
3) 复合享元角色:它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象
的组合。
4) 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关
键!
5) 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
(二)示例代码
应用场景:驾校的车,要供所有学员开,每位学员上车需要把自己对应的驾驶座调整为自己最舒适的位置(前后上下距离调整好)
抽象享元角色 – AbstractCar(车)
public abstract class AbstractCar{
//汽车品牌(内蕴)
protected String brand;
//汽车价格(内蕴)
protected int price;
//驾驶座前距(外蕴)
protected int toFrontDistance;
//驾驶座高低(外蕴)
protected int height;
//司机姓名(外蕴)
protected String name;
//设置外蕴状态
protected abstract void setExtrinsicState(String name, int height, int toFrontDistance);
// 显示车子信息
public void show() {
System.out.println ("品牌:"+this.brand +",价格:"+this.price+",司机:"+this.name+
",座位位置数据:(" + this.toFrontDistance + "," + this.height + ")");
}
}
具体享元角色 – ConcreteCar
//具体汽车
public class ConcreteCar extends AbstractCar {
/**
* 构造方法 初始化内蕴属性
*/
public ConcreteCar(String brand, int price) {
this.brand = brand;
this.price = price;
}
/**
* 设置外蕴状态
* @param name
* @param height
* @param toFrontDistance
*/
@Override
public void setExtrinsicState(String name, int height, int toFrontDistance){
this.name = name;
this.height = height;
this.toFrontDistance = toFrontDistance;
}
}
享元工厂角色 – CarFactory(汽车享元工厂)
public class CarFactory{
private static CarFactory carFactory = new CarFactory ();
// 缓存存放共享对象
private final Hashtable<String, AbstractCar> cache = new Hashtable ();
private CarFactory() {
}
public static CarFactory getInstance() {
return carFactory;
}
/**
* 学员根据品牌选择车子进行练习
* @param brand
* @param name
* @param toFrontLength
* @param height
* @return
*/
public AbstractCar driveCar(String brand, String name, int toFrontLength, int height) {
// 从缓存中获得棋子对象实例
AbstractCar abstractCar = this.cache.get (brand);
if (abstractCar == null) {
// 缓存中没有车子实例信息 则创建车子实例 并放入缓存
switch (brand) {
case "Kia":
//起亚
abstractCar = new ConcreteCar("起亚", 100000);
break;
case "Honda":
//本田
abstractCar = new ConcreteCar("本田", 150000);
break;
default:
abstractCar = new ConcreteCar("捷达", 50000);
break;
}
if(abstractCar != null){
cache.put(brand, abstractCar);
}
}
//设置外蕴属性
abstractCar.setExtrinsicState (name, height, toFrontLength);
return abstractCar ;
}
}
测试:
public class Main {
public static void main(String[] args) {
CarFactory carFactory = CarFactory.getInstance();
carFactory.driveCar("Kia","张三", 50, 20).show();
carFactory.driveCar("Honda","李四", 45, 25).show();
carFactory.driveCar("Honda","王五", 50, 25).show();
}
}
//品牌:起亚,价格:100000,司机:张三,座位位置数据:(50,20)
//品牌:本田,价格:150000,司机:李四,座位位置数据:(45,25)
//品牌:本田,价格:150000,司机:王五,座位位置数据:(50,25)
(三)享元模式的优劣
享元模式优点就在于它能够大幅度的降低内存中对象的数量;
而为了做到这一步也带来了它的缺点:它使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。
所以一定要切记使用享元模式的条件:
1)系统中有大量的对象,他们使系统的效率降低。
2)这些对象的状态可以分离出所需要的内外两部分。
外蕴状态和内蕴状态的划分以及两者关系的对应也是非常值得重视的。只有将内外划分妥当才能使内蕴状态发挥它应有的作用;如果划分失误,在最糟糕的情况下系统中的对象是一个也不会减少的!两者的对应关系的维护和查找也是要花费一定的空间(当然这个比起不使用共享对象要小得多)和时间的,可以说享元模式就是使用时间来换取空间的。