23中设计模式之享元模式

#动态代理模式

#装饰者模式

#外观模式

享元模式的定义

享元模式是池技术的重要实现方式,使用共享对象可有效地支持大量的细粒度对象。
扩展:享元模式要求细粒度对象和共享对象。我们知道分配太多的对象到应用程序中将有损程序的性能,同时还容易造成内存溢出,那怎么避免呢?就是享元模式提到的共享技术。我们先来了解一下对象的内部状态和外部状态。

  • 内部状态:
    内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随着环境改变而改变,它们可以作为一个对象的动态附加信息,不必直接存储在具体某个对象中,属于可以共享的部分。
  • 外部状态
    外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态,它是一批对象的统一标示符,是唯一的一个索引值。

享元模式的类图:
在这里插入图片描述

  • Flyweight—抽象享元角色
    简单地说就是一个产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现
  • ConcreteFlyweigth—具体享元角色
    具体的一个产品类实现抽象角色定义的业务。该角色中需要注意的是内部状态处理应该与环境无关,不应该出现一个操作改变了内部状态同时修改了外部状态,这是绝对不允许的。
  • unsharedConcreteFlyweight—不可以共享的享元角色
    不存在外部状态或者安全要求不能够使用共享技术的对象,该对象一般不会出现在享元工厂中。
  • FlyweightFactory—享元工厂
    职责非常简单,就是构造一个池容器,同时提供从池中获得对象的方法。
    享元模式的目的在于运用共享技术,是得一些细粒度的对象可以共享,多使用细粒度的对象,便于重用或重构。
//真是享元角色
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(){
   // 业务逻辑处理
   }
}
//实现自己的业务逻辑,然后接受外部状态,以便内部业务逻辑对外部状态依赖。在抽象享元中对外部状态加上了final关键字
//享元工厂
public class FlyweightFactory{
// 定义一个池容器
private static HashMap<String,Flyweight>pool = new HashMap<String,Flyweight>;
//享元工厂  e是外部状态
public static Flyweight getFlyweight(String e){
// 需要返回的对象
Flyweight flywight  = null;
//在池中没有该对象
if(pool.containsKey(e)){
flyweight = pool.get(e);
}else{
// 根据外部状态创建享元对象
flyweight = new ConcreteFlyweight1(e);
//放置到池中
pool.put(e,flyweight);
}
return flyweight;
}
}
享元模式的优缺点

享元模式是一个非常简单的模式,它可以大大减少应用程序创建的对象,降低程序内存
的占用,增强程序的性能,但它同时也提高了系统复杂性,需要分离出外部状态和内部状
态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混
乱。

享元模式的扩展
性能平衡

尽量使用Java基本类型作为外部状态。在报考系统中,我们不考虑系统的修改风险,完
全可以重新建立一个类作为外部状态,因为这才完全符合面向对象编程的理念。好,我们实
现处理,先看类图,如图28-4所示。
在这里插入图片描述
代码清单28-13 外部状态类

 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
}
return false;
}
@Override
public int hashCode(){
return subject.hashCode() + location.hashCode();
}
}

注意,一定要覆写equals和hashCode方法,否则它作为HashMap中的key值是根本没有意
义的,只有hashCode值相等,并且equals返回结果为true,两个对象才相等,也只有在这种情
况下才有可能从对象池中查找获得对象。
注意 如果把一个对象作为Map类的键值,一定要确保重写了equals和hashCode方法,
否则会出现通过键值搜索失败的情况,例如map.get(object)、map.contains(object)等会返回失
败的结果。
SignInfo的修改较小,仅在SignInfo中引入该ExtrinsicState外部状态对象,在此不再赘述。
我们再来看享元工厂,它是以ExtrinsicState类作为外部状态,如代码清单28-14所示。
代码清单28-14 享元工厂

public class SignInfoFactory {
//池容器
private static HashMap<ExtrinsicState,SignInfo> pool = new HashMap <Extrinsi
//从池中获得对象
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;
}
}

重点是看看我们的场景类,我们来测试一下性能差异,如代码清单28-15所示。
代码清单28-15 场景类

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
同样,我们看看以String类型作为外部状态的运行情况,如代码清单28-16所示。
代码清单28-16 场景类

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
看到没?一半的效率,这还是非常简单的享元对象,看看我们重写的equals方法和
hashCode方法,这段代码是必须实现的,如果比较复杂,这个时间差异会更大。
各位,想想看,使用自己编写的类作为外部状态,必须覆写equals方法和hashCode方
法,而且执行效率还比较低,这种吃力不讨好的事情最好别做,外部状态最好以Java的基本
类型作为标志,如String、int等,可以大幅地提升效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值