享元模式

前言

享元模式也是一种结构型模式。

享元模式的原理和实现都很简单,但是应用场景却相对狭窄,它和现在我们所熟知的缓存模式、池化模式有所联系,却又有不同。

享元模式强调的是空间效率,比如,一个很大的数据模型对象如何尽量少占用内存并提供可复用的能力;而缓存模式强调的是时间效率,比如,缓存秒杀的活动数据和库存数据等,数据可能会占用很多内存和磁盘空间,但是得保证在大促活动开始时要能及时响应用户的购买需求。也就是说,两者本质上解决的问题类型是不同的。

目录

一、定义

使用共享对象可有效地支持大量地细粒度对象。

摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,从而让我们能在有限的内存容量中载入更多对象。

从这个定义中你可以发现,享元模式要解决的核心问题就是节约内存空间,使用的办法是找出相似对象之间的共有特征,然后复用这些特征。

二、模式原理分析

先了解下内部状态和外部状态的概念

  • 内部状态:不会随环境改变而改变的状态,俗称不可变对象。比如,在 JavaInteger 对象初始化就是缓存 -127 到 128 的值,无论怎么使用 Integer,这些值都不会变化。

  • 外部状态:随环境改变而改变的状态。通常是某个对象所独有的,不能被共享,因此,也只能由客户端保存。之所以需要外部状态就是因为客户端需要不同的定制化操作。

    //享元类
    public interface Flyweight {
    void operation(int state);
    }

    //享元工厂类
    public class FlyweighFactory {
    // 定义一个池容器
    public Map<String,Flyweight> pool = new HashMap<>();

    public FlyweighFactory() {
        pool.put("A", new ConcreteFlyweight("A"));//将对应的内部状态添加进去
        pool.put("B", new ConcreteFlyweight("B"));
        pool.put("C", new ConcreteFlyweight("C"));
    }
    
    //根据内部状态来查找值
    public Flyweight getFlyweight(String key) {
        if (pool.containsKey(key)) {
            System.out.println("===享元池中有,直接复用,key:"+key);
            return pool.get(key);
        } else {
            System.out.println("===享元池中没有,重新创建并复用,key:"+key);
            Flyweight flyweightNew = new ConcreteFlyweight(key);
            pool.put(key,flyweightNew);
            return flyweightNew;
        }
    }
    复制代码

    }

    //共享的具体享元类
    public class ConcreteFlyweight implements Flyweight {
    private String uniqueKey;
    public ConcreteFlyweight(String key) {
    this.uniqueKey = key;
    }

    @Override
    public void operation(int state) {
        System.out.printf("=== 享元内部状态:%s,外部状态:%s%n",uniqueKey,state);
    }
    复制代码

    }
    //非共享的具体享元类
    public class UnsharedConcreteFlyweight implements Flyweight {

    private String uniqueKey;
    
    public UnsharedConcreteFlyweight(String key) {
        this.uniqueKey = key;
    }
    
    @Override
    public void operation(int state) {
        System.out.println("=== 使用不共享的对象,内部状态:"+uniqueKey+",外部状态:"+state);
    }
    复制代码

    }

就是利用Map缓存了应该被缓存的类,key 相同就复用对象。

Java中也有类似操作,比如,在 JavaInteger 对象初始化就是缓存 -127 到 128 的值,无论怎么使用 Integer,这些值都不会变化。

public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
		return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
复制代码

三、使用场景

  • 系统中存在大量重复创建的对象。比如,同一个商品的展示图片、详情介绍、文字介绍等

  • 相关性很高并且可以复用的对象。比如,公司的组织结构人员基本信息、网站的分类信息

  • 需要缓冲池的场景

四、优点

  • 减少内存消耗,节省服务器成本

  • 聚合同一类的不可变对象,提高对象复用性

五、缺点

  • 以时间换空间,间接增加了系统的实现复杂度,提高了系统的复杂性,需要分离出外部状态和内部状态,当使用自己编写的类作为外部状态时,必须重写equalshashCode方法,所以外部状态最好以Java基本类型为标志
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享