1.定义
使用共享对象可有效地支持大量的细粒度的对象。
是对象池技术的重要实现方式。
2.享元模式的使用场景
- 系统中存在大量的相似对象。
- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。
- 需要缓冲池的场景。
请看例子程序,学生和学校的关系:
package _22FlyweightPattern;
/**
* 班级类
*/
public class School {
// 学校id
private int id;
// 学校名称
private String schoolName;
// 某学生对该学校的映像
private String desc;
// 还有很多信息,比较占内存…………
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
package _22FlyweightPattern;
/**
* 学生类
*/
public class Student {
// 学生id
private int id;
// 学生姓名
private String name;
// 学生家庭住址
private String homeAddress;
// 所在学校
private School school;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHomeAddress() {
return homeAddress;
}
public void setHomeAddress(String homeAddress) {
this.homeAddress = homeAddress;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
}
由于多个学生可以共用一个School对象,因此我们使用享元模式来分析School类。
我们将一个可共用的细粒度对象(上例中的School对象)的信息分为两类
- 内部状态:类似于上面School类中的id、schoolName等等,不会随着环境的变化而变化。
- 外部状态:类似于上面School类中的desc属性,但是要注意,内部状态和外部状态不应该互相影响。外部状态与对象本身无必然关系,外部状态总是因为外界环境的改变而变化,也就是说外部状态是由外界环境来决定的,无论你对学校的映像如何,学校都是同一个。
所谓的享元模式就是将一个类中可共享的部分抽取出来放进对象池,以避免重复创建相同(业务意义上的相同)的对象。
也就是下图所表示的,要将第一张图重构成第二张图:
3.享元模式的四个角色
享元角色就是可共享的角色。
- Flyweight-抽象享元角色:它简单的说就是一个产品的抽象类,同事定义了对象的外部状态和内部状态的接口或实现。
- ConcreteFlyweight-具体享元角色:具体的一个产品类,实现抽象角色定义的业务。该角色需要注意的是内部状态处理应该与环境无关,不应该出现一个操作改变了内部状态,同事又改变了外部状态,这是绝对不允许的。(使用享元模式尽量不要修改内部状态)
- unsharedConcreteFlyweight不可共享的享元角色:一般由N个具体享元角色组合而成,因此它本身不需要被缓存,不必出现在享元工厂中。
- FlyweightFactory享元工厂:职责非常简单,就是构造一个池容器,同事提供从池中获得对象的方法。
下面是享元模式的类图:
4.享元模式的通用代码
package _22FlyweightPattern;
/**
* 抽象享元角色
*/
public abstract class Flyweight {
// 内部状态
private String intrinsic;
// 外部状态
protected final String extrinsic;
// 强制享元角色必须接受外部状态
public Flyweight(String extrinsic)
{
this.extrinsic = extrinsic;
}
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
// 定义业务逻辑
public abstract void operate();
}
package _22FlyweightPattern;
/**
* 具体享元角色
*/
public class ConcreteFlyweight extends Flyweight {
public ConcreteFlyweight(String extrinsic) {
super(extrinsic);
}
@Override
public void operate() {
// 业务逻辑
}
}
package _22FlyweightPattern;
import java.util.HashMap;
import java.util.Map;
/**
* 享元工厂
*/
public class FlyweightFactory {
// 定义一个池容器
private static Map<String, Flyweight> pool = new HashMap<String, Flyweight>();
// 享元工厂
// 线程安全问题需要另外解决
public static Flyweight getFlyweight(String extrinsic)
{
// 需要返回的对象
Flyweight flyweight = null;
// 先在池中查找对象
if(pool.containsKey(extrinsic))
{
flyweight = pool.get(extrinsic);
}else {
flyweight = new ConcreteFlyweight(extrinsic);
pool.put(extrinsic, flyweight);
}
return flyweight;
}
}
5.享元模式的优缺点
享元模式是一个非常简单的模式,它可以大大减少应用程序创建的对象(比如你使用new School()的方式10000个学生对象就会产生10000个School对象,而通过缓存技术,那么只存在一个School对象),降低程序内存的占用,增强程序的性能,但它同时也提高了系统复杂性,需要分理处内部和外部状态,而且外部状态具有固化特效,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。
6.最佳实践
外部状态最好使用Java的基本类型作为标志,如String、int等,可以大幅提升效率(Map的key值)。
享元模式在Java API中随处可见,比如String会对字符串进行缓存,Integer会对-128到127的数字进行缓存。String的intern方法:如果String对象池中有该类型的值,那么直接返回对象池中的对象。