1.定义
享元模式(Flyweight Pattern)是池技术的重要实现方式,其定义如下:Use sharing to support large numbers of fine-grained objects efficiently.(使用共享对象可有效地支持大量的细粒度的对象。)
- 内部状态
内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变。
- 外部状态
外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态。
2.类图
- Flyweight——抽象享元角色
它简单地说就是一个产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现。
- ConcreteFlyweight——具体享元角色
具体的一个产品类,实现抽象角色定义的业务。该角色中需要注意的是内部状态处理应该与环境无关,不应该出现一个操作改变了内部状态,同时修改了外部状态,这是绝对不允许的。
- unsharedConcreteFlyweight——不可共享的享元角色
不存在外部状态或者安全要求(如线程安全)不能够使用共享技术的对象,该对象一般不会出现在享元工厂中。
- FlyweightFactory——享元工厂
职责非常简单,就是构造一个池容器,同时提供从池中获得对象的方法。
3.代码
/**
* 抽象享元角色
*/
public abstract class Flyweight {
//内部状态
private String intrinsic;
//外部状态
protected final String Extrinsic;
//要求享元角色必须接受外部状态
public Flyweight(String _Extrinsic){
this.Extrinsic = _Extrinsic;
}
//定义业务操作
public abstract void operate();
//内部状态的getter/setter
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
}
/**
* 具体享元角色
*/
public class ConcreteFlyweight1 extends Flyweight{
//接受外部状态
public ConcreteFlyweight1(String _Extrinsic){
super(_Extrinsic);
}
//根据外部状态进行逻辑处理
public void operate(){
//业务逻辑
}
}
public class ConcreteFlyweight2 extends Flyweight{
//接受外部状态
public ConcreteFlyweight2(String _Extrinsic){
super(_Extrinsic);
}
//根据外部状态进行逻辑处理
public void operate(){
//业务逻辑
}
}
/**
* 享元工厂
*/
public class FlyweightFactory {
//定义一个池容器
private static HashMap<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 ConcreteFlyweight1(Extrinsic);
//放置到池中
pool.put(Extrinsic, flyweight);
}
return flyweight;
}
}
4.优点和缺点
享元模式是一个非常简单的模式,它可以大大减少应用程序创建的对象,降低程序内存的占用,增强程序的性能,但它同时也提高了系统复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。
5.使用场景
- 系统中存在大量的相似对象。
- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。
- 需要缓冲池的场景。
6.扩展
6.1 线程安全的问题
/**
* 报考信息工厂
*/
public class SignInfoFactory {
//池容器
private static HashMap<String,SignInfo> pool = new HashMap<String,SignInfo>();
//从池中获得对象
public static SignInfo getSignInfo(String key){
//设置返回对象
SignInfo result = null;
//池中没有该对象,则建立,并放入池中
if(!pool.containsKey(key)){
result = new SignInfo();
pool.put(key, result);
}else{
result = pool.get(key);
}
return result;
}
}
/**
* 多线程场景
*/
public class MultiThread extends Thread {
private SignInfo signInfo;
public MultiThread(SignInfo _signInfo){
this.signInfo = _signInfo;
}
public void run(){
if(!signInfo.getId().equals(signInfo.getLocation())){
System.out.println("编号:"+signInfo.getId());
System.out.println("考试地址:"+signInfo.getLocation());
System.out.println("线程不安全了!");
}
}
}
/**
* 场景类
*/
public class Client {
public static void main(String[] args) {
//在对象池中初始化4个对象
SignInfoFactory.getSignInfo("科目1");
SignInfoFactory.getSignInfo("科目2");
SignInfoFactory.getSignInfo("科目3");
SignInfoFactory.getSignInfo("科目4");
//取得对象
SignInfo signInfo = SignInfoFactory.getSignInfo("科目2");
while(true){
signInfo.setId("ZhangSan");
signInfo.setLocation("ZhangSan");
(new MultiThread(signInfo)).start();
signInfo.setId("LiSi");
signInfo.setLocation("LiSi");
(new MultiThread(signInfo)).start();
}
}
}
模拟实际的多线程情况,在对象池中我们保留4个对象,然后启动N多个线程来模拟,我们马上就看到如下的提示:
编号:LiSi
考试地址:ZhangSan
线程不安全了!
看看,线程不安全了吧,这是正常的,设置的享元对象数量太少,导致每个线程都到对象池中获得对象,然后都去修改其属性,于是就出现一些不和谐数据。只要使用Java开发,线程问题是不可避免的,那我们怎么去避免这个问题呢?享元模式是让我们使用共享技术,而Java的多线程又有如此问题,该如何设计呢?没什么可以参考的标准,只有依靠经验,在需要的地方考虑一下线程安全,在大部分的场景下都不用考虑。我们在使用享元模式时,对象池中的享元对象尽量多,多到足够满足业务为止。
6.2 性能平衡
/**
* 外部状态类
*/
public class ExtrinsicState {
//考试科目
private String subject;
//考试地点
private String location;
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
@Override
public boolean equals(Object obj){
if(obj instanceof ExtrinsicState){
ExtrinsicState state = (ExtrinsicState)obj;
return state.getLocation().equals(location) && state.getSubject().equals(subject);
}
return false;
}
@Override
public int hashCode(){
return subject.hashCode() + location.hashCode();
}
}
/**
* 享元工厂
*/
public class SignInfoFactory {
//池容器
private static HashMap<ExtrinsicState,SignInfo> pool = new HashMap <ExtrinsicState,SignInfo>();
//从池中获得对象
public static SignInfo getSignInfo(ExtrinsicState key){
//设置返回对象
SignInfo result = null;
//池中没有该对象,则建立,并放入池中
if(!pool.containsKey(key)){
result = new SignInfo();
pool.put(key, result);
}else{
result = pool.get(key);
}
return result;
}
}
/**
* 场景类
*/
public class Client {
public static void main(String[] args) {
//初始化对象池
ExtrinsicState state1 = new ExtrinsicState();
state1.setSubject("科目1");
state1.setLocation("上海");
SignInfoFactory.getSignInfo(state1);
ExtrinsicState state2 = new ExtrinsicState();
state2.setSubject("科目1");
state2.setLocation("上海");
//计算执行100万次需要的时间
long currentTime = System.currentTimeMillis();
for(int i=0;i<1000000;i++){
SignInfoFactory.getSignInfo(state2);
}
long tailTime = System.currentTimeMillis();
System.out.println("执行时间:"+(tailTime - currentTime) + " ms");
}
}
运行结果如下所示:
执行时间:172 ms
/**
* 场景类
*/
public class Client {
public static void main(String[] args) {
String key1 = "科目1上海";
String key2 = "科目1上海";
//初始化对象池
SignInfoFactory.getSignInfo(key1);
//计算执行10万次需要的时间
long currentTime = System.currentTimeMillis();
for(int i=0;i<10000000;i++){
SignInfoFactory.getSignInfo(key2);
}
long tailTime = System.currentTimeMillis();
System.out.println("执行时间:"+(tailTime - currentTime) + " ms");
}
}
运行结果如下所示:
执行时间:78 ms