前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式 | 结构型 | 享元模式

设计模式 | 结构型 | 享元模式

作者头像
被水淹没
发布2023-02-25 19:10:25
1840
发布2023-02-25 19:10:25
举报
文章被收录于专栏:迈向架构师

享元模式(Flyweight)

享元模式(Flyweight)

介绍

享元模式是一种结构型设计模式。

享元模式顾名思义就是被共享的单元,意图是复用对象节省内存

适用场景

  • 存在大量重复对象(重复状态)且没有足够的内存容量时使用享元模式。
  • ...

优缺点

优点:

  • 节省大量内存。

缺点:

  • 代码复杂度提升。
  • 如果对象有不同的情景数据(外在状态),调用者需要耗费时间来重新计算。

与其他模式的关系

  • 可以使用享元模式实现组合模式树的共享叶节点以节省内存。
  • 如果能将对象的所有共享状态简化为一个享元对象,那么享元就和单例类似了。但这两个模式有两个根本性的不同。
    • 单例只会有一个单例实体。
    • 享元可以有多个实体,各实体的内在状态也可以不同。
    • 单例对象可以是可变的。
    • 享元对象是不可变的。

实现方式

  1. 将享元对象划分为2部分:
    • 内在状态(不变的部分)
    • 外在状态(改变的部分)
  2. 保留类中表示内在状态的成员变量,并将其设置为不可修改
  3. 客户端必须存储和计算外在状态(情景)的数值,因为只有这样才能调用享元对象的方法。
    • 为了使用方便,外在状态和引用享元的成员变量可以移动到单独的情景类中。
  4. 可以创建工厂类来管理享元缓存池,如果使用了工厂,那么客户端就只能通过工厂来获取享元对象。

示例

享元对象:抽取出来的骰子类(只包含不变的内在属性)

代码语言:javascript
复制
public class DiceType {

    // 内在属性:面数
    private int faceValue;
    private SecureRandom random = new SecureRandom();

    public DiceType(int faceValue) {
        this.faceValue = faceValue;
    }

    public int roll() {
        int i = random.nextInt(faceValue);
        return i + 1;
    }
}

情景类:存储着外在状态的骰子类(包含变化的部分以及享元对象)

代码语言:javascript
复制
public class Dice {

    // 外在属性:幸运数字
    private int luckyNumber;
    // 外在属性:不幸数字
    private int unluckyNumber;

    // 共享的骰子
    private DiceType diceType;

    // 这一次 roll 出来的数字
    private int rollNumber;
    // 这一次 roll 出来的幸运类型: 0普通 1幸运 -1不幸
    private int luckyType;

    public Dice(int luckyNumber, int unluckyNumber, DiceType diceType) {
        this.luckyNumber = luckyNumber;
        this.unluckyNumber = unluckyNumber;
        this.diceType = diceType;
    }

    // 掷骰子
    public int roll() {
        int roll = diceType.roll();
        rollNumber = roll;
        if (roll == luckyNumber) {
            System.out.println("投出了幸运数字:" + roll);
            luckyType = 1;
        } else if (roll == unluckyNumber) {
            System.out.println("投出了不幸数字:" + roll);
            luckyType = -1;
        } else {
            System.out.println("投出了数字:" + roll);
            luckyType = 0;
        }
        return roll;
    }

    // 获取幸运状态:0普通 1幸运 -1不幸
    public int getLuckyType() {
        return luckyType;
    }
}

享元缓存工厂

代码语言:javascript
复制
public class DiceFactory {

    private static final Map<Integer, DiceType> diceTypeMap = new HashMap<>();

    /**
     * 获取享元对象:获取 N面骰子 实例
     */
    public static DiceType getDiceType(int faceValue) {
        DiceType result = diceTypeMap.get(faceValue);
        if (result == null) {
            DiceType diceType = new DiceType(faceValue);
            diceTypeMap.put(faceValue, diceType);
            result = diceType;
        }
        return result;
    }
}

测试代码

代码语言:javascript
复制
public class FlyweightTest {

    @Test
    public void test() {

        // 获取享元对象:6面骰子
        DiceType diceType1 = DiceFactory.getDiceType(6);

        // 幸运6 不幸1 其他正常
        int luckyNumber = 6;
        int unluckyNumber = 1;

        Dice dice1 = new Dice(luckyNumber, unluckyNumber, diceType1);
        for (int i = 0; i < 100; i++) {
            int rollNumber = dice1.roll();;
            int luckyType = dice1.getLuckyType();
            if (rollNumber==luckyNumber) {
                Assertions.assertEquals(1,luckyType);
            }else if(rollNumber==unluckyNumber){
                Assertions.assertEquals(-1,luckyType);
            }else{
                Assertions.assertEquals(0,luckyType);
            }
        }

        // 再次获取享元对象 ,一样是6面骰子
        DiceType diceType2 = DiceFactory.getDiceType(6);

        // 获取的对象应该是同一个
        Assertions.assertEquals(diceType1,diceType2);

        // 改一下外在状态
        luckyNumber = 3;
        unluckyNumber = 2;

        Dice dice2 = new Dice(luckyNumber, unluckyNumber, diceType2);
        for (int i = 0; i < 100; i++) {
            int rollNumber = dice2.roll();;
            int luckyType = dice2.getLuckyType();
            if (rollNumber==luckyNumber) {
                Assertions.assertEquals(1,luckyType);
            }else if(rollNumber==unluckyNumber){
                Assertions.assertEquals(-1,luckyType);
            }else{
                Assertions.assertEquals(0,luckyType);
            }
        }
    }
}

实例

JDK

包装器类型

java.lang.Integer#valueOf(int)

Long、Short、Byte、Boolean、Character、BigDecimal 等。

代码语言:javascript
复制
public final class Integer extends Number implements Comparable<Integer> {
    // ... 省略

    /**
     * 默认缓存 -128~127 
     * 可通过以下两者方法之一 改掉最大值127
     * -Djava.lang.Integer.IntegerCache.high=255
     * -XX:AutoBoxCacheMax=255
     */
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                    VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

    // 转为包装对象
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    // ... 省略
}
java.lang.String

String 类利用享元模式来复用相同的字符串常量。

JVM 会专门开辟一块存储区来存储字符串常量,这块存储区叫作“字符串常量池”。

以上代码与文章会同步到 github 仓库:

/chenbihao/Design-Patterns

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 迈向架构师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 享元模式(Flyweight)
    • 介绍
      • 适用场景
        • 优缺点
          • 与其他模式的关系
            • 实现方式
              • 示例
                • 实例
                  • JDK
              相关产品与服务
              对象存储
              对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档