* 24种设计模式——享元模式

xiaoxiao2021-02-28  108

核心:使用共享对象可有效地支持大量的细粒度的对象。运用共享技术,使得一些细粒度的对象可以共享。

一、报名系统crash多台机器

报考系统crash,原因是使用了工厂模式来获取对象,在大访问量100万时,就会有100万个对象,因为JVM回收不及时,导致内存OutOfMemory,这里我们可以把对象获取换成一种"池"的形式

1.报考对象

public class SignInfo { //报名人员的ID private String id; //考试地点 private String location; //考试科目 private String subject; //邮寄地址 private String postAddress; get/set() } 2. 带对象池的报考信息

考试科目和考试地点是有限的,我们可以把它做为key,在池中生成有限的对象。

public class SignInfo4Pool extends SignInfo{ //定义一个对象池提取的KEY值 private String key; //构造函数获得相同标志 public SignInfo4Pool(String key) { this.key = key; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } } 3. 带对象池的工厂类

public class SignInfoFactory { //池容器 private static Map<String,SignInfo> pool = new HashMap<String,SignInfo>(); //报名信息的对象工厂 @Deprecated public static SignInfo getSignInfo(){ return new SignInfo(); } //从池中获得对象 public static SignInfo getSignInfo(String key){ //设置返回对象 SignInfo result = null; //池中没有该对象,则建立,并放入池中 if(!pool.containsKey(key)){ System.out.println(key+"----建立对象,并放置到池中"); result = new SignInfo4Pool(key); pool.put(key, result); }else{ result = pool.get(key); System.out.println(key+"----直接从池中取得"); } return result; } } 4. 场景类

public class Client { public static void main(String[] args) { //初始化对象池 for(int i = 0;i < 4;i++){ String subject = "科目"+i; //初始化地址 for(int j = 0;j < 30;j++){ String key = subject + "考试地点"+j; SignInfoFactory.getSignInfo(key); } } //System.out.println(SignInfoFactory.pool.size());//120 SignInfo signInfo = SignInfoFactory.getSignInfo("科目1考试地点1"); } }

==>

科目3考试地点24----建立对象,并放置到池中 科目3考试地点25----建立对象,并放置到池中 科目3考试地点26----建立对象,并放置到池中 科目3考试地点27----建立对象,并放置到池中 科目3考试地点28----建立对象,并放置到池中 科目3考试地点29----建立对象,并放置到池中

............ 科目1考试地点1----直接从池中取得

通过改造后,内存中就只有120个对象,相比之前100万个SignInfo对象优化了非常多。

二、享元模式的定义

享元模式是池技术的重要实现方式。

定义:使用共享对象可有效地支持大量的细粒度的对象。 享元模式中,要求细粒度对象,那么不可避免地使得对象数量多且性质相近,那我们就将这些对象的信息分为两个部分:内部状态(intrinsic)与外部状态(extrinsic)。

a. 内部状态

内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变,如我们例子中的id/postAddress等,它们可以作为一个对象的动态附加信息,不必直接储存在具体某个对象中,属于可以共享的部分。 b. 外部状态

外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态,如我们例子中的考试科目+考试地点复合字符串,它是一批对象的统一标识,是唯一的一个索引值。 1. 抽象享元角色

public abstract class Flyweight { //内部状态 private String intrinsic; //外部状态 protected final String Extrinsic; //要求享元角色必须接受外部状态 public Flyweight(String extrinsic) { Extrinsic = extrinsic; } //定义业务操作 public abstract void operate(); //内部状态的getter/setter public String getIntrinsic() { return intrinsic; } public void setIntrinsic(String intrinsic) { this.intrinsic = intrinsic; } } 2. 具体享元角色

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() { //业务逻辑 } } 3. 享元工厂

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 ConcreteFlyweight1(extrinsic); //放置到池中 pool.put(extrinsic, flyweight); } return flyweight; } }

三、享元模式的应用

1. 优点和缺点

降低内存的占用,但同时也提高了系统复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。

2. 使用场景 

1) 系统中存在大量的相似的对象

2) 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。

3) 需要缓冲池的场景

四、享元模式扩展

1. 线程安全问题

如果不和例子中一样使用“考试科目+考试地点”作为外部状态,而只使用“考试科目”或者“考试地点”作为外部状态呢,这样池中的对象会更少,运行可以是可以,但是池中设置的享元对象数量太少,导致每个线程都到对象池中获得对象,然后都去修改其属性,于是就出现一些不和谐的数据,所以在使用享元模式时,对象池中的享元对象尽量多,多到足够满足业务为止。

1)报考信息工厂

public class SignInfoFactory { //池容器 private static Map<String,SignInfo> pool = new HashMap<String,SignInfo>(); //报名信息的对象工厂 @Deprecated public static SignInfo getSignInfo(){ return new SignInfo(); } //从池中获得对象 public static SignInfo getSignInfo(String key){ //设置返回对象 SignInfo result = null; //池中没有该对象,则建立,并放入池中 if(!pool.containsKey(key)){ System.out.println(key+"----建立对象,并放置到池中"); result = new SignInfo(); // result = new SignInfo4Pool(key); pool.put(key, result); }else{ result = pool.get(key); System.out.println(key+"----直接从池中取得"); } return result; } }2)多线程场景 

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("线程不安全了"); } } }3)场景类

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"); signInfo.setId("123"); SignInfo signInfo1 = SignInfoFactory.getSignInfo("科目2"); System.out.println(signInfo1.getId()); } } 2. 性能平衡

尽量使用Java基本类型作为外部状态(即HashMap中的Key),如果将例子中的外部状态“科目”和“考点”用类封装起来,这样似乎更符合面向对象,但因为是外部状态,是HashMap的key,所以要重写类中的equals和hashCode,这样才可以使用map的put或者get等,这样执行效率就会大大下降。 1)外部状态类

public class ExtrinsicState { //考试科目 private String subject; //考试地点 private String location; get/set(); public boolean equals(Object obj) { if(obj instanceof ExtrinsicState){ ExtrinsicState state = (ExtrinsicState) obj; return state.getLocation().equals(location) && state.getSubject().equals(subject); } return false; } public int hashCode() { return subject.hashCode() + location.hashCode(); } }2)享元工厂

public class SignInfoFactory { //池容器 private static Map<ExtrinsicState,SignInfo> pool = new HashMap<ExtrinsicState,SignInfo>(); //报名信息的对象工厂 @Deprecated public static SignInfo getSignInfo(){ return new SignInfo(); } //从池中获得对象 public static SignInfo getSignInfo(ExtrinsicState key){ //设置返回对象 SignInfo result = null; //池中没有该对象,则建立,并放入池中 if(!pool.containsKey(key)){ System.out.println(key+"----建立对象,并放置到池中"); result = new SignInfo(); pool.put(key, result); }else{ result = pool.get(key); } return result; } }3)场景类

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); } }==》103ms

不使用外部状态类,用String类型代替

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 < 1000000;i++){ SignInfoFactory.getSignInfo(key2); } long tailTime = System.currentTimeMillis(); System.out.println(tailTime-currentTime); } } ==》50ms

各位想想,使用自己编写的类作为外部状态,必须重写equals方法和hashCode方法,而且执行效率还比较低,这种吃力不讨好的事情最好别做,外部状态最好以Java的基本类型作为标志,如String、int等,可以大幅提升效率。

转载请注明原文地址: https://www.6miu.com/read-21963.html

最新回复(0)