22.享元模式(Flyweight Pattern)

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属性,但是要注意,内部状态和外部状态不应该互相影响。外部状态与对象本身无必然关系,外部状态总是因为外界环境的改变而变化,也就是说外部状态是由外界环境来决定的,无论你对学校的映像如何,学校都是同一个。

所谓的享元模式就是将一个类中可共享的部分抽取出来放进对象池,以避免重复创建相同(业务意义上的相同)的对象。
也就是下图所表示的,要将第一张图重构成第二张图:

 

22.享元模式(Flyweight Pattern)

 

22.享元模式(Flyweight Pattern)

 

3.享元模式的四个角色

享元角色就是可共享的角色。

  • Flyweight-抽象享元角色:它简单的说就是一个产品的抽象类,同事定义了对象的外部状态和内部状态的接口或实现。
  • ConcreteFlyweight-具体享元角色:具体的一个产品类,实现抽象角色定义的业务。该角色需要注意的是内部状态处理应该与环境无关,不应该出现一个操作改变了内部状态,同事又改变了外部状态,这是绝对不允许的。(使用享元模式尽量不要修改内部状态)
  • unsharedConcreteFlyweight不可共享的享元角色:一般由N个具体享元角色组合而成,因此它本身不需要被缓存,不必出现在享元工厂中。
  • FlyweightFactory享元工厂:职责非常简单,就是构造一个池容器,同事提供从池中获得对象的方法。

下面是享元模式的类图:

 

22.享元模式(Flyweight Pattern)

 

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对象池中有该类型的值,那么直接返回对象池中的对象。

你可能感兴趣的:(Pattern,flyweight,享元模式)