22——享元模式

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值