【设计模式】享元模式(Flyweight)详解:用共享对象对抗内存爆炸

文章目录

    • [1. 引言:当对象多到撑爆内存](#1. 引言:当对象多到撑爆内存)
    • [2. 什么是享元模式](#2. 什么是享元模式)
      • [GoF 定义](#GoF 定义)
    • [3. 享元模式的核心思想](#3. 享元模式的核心思想)
    • [4. 享元模式的结构](#4. 享元模式的结构)
    • [5. 示例:字符系统](#5. 示例:字符系统)
      • [5.1 享元接口](#5.1 享元接口)
      • [5.2 具体享元](#5.2 具体享元)
      • [5.3 享元工厂](#5.3 享元工厂)
      • [5.4 客户端使用](#5.4 客户端使用)
    • [6. 享元模式的优点](#6. 享元模式的优点)
    • [7. 享元模式的缺点](#7. 享元模式的缺点)
    • [8. 享元 vs 对象池](#8. 享元 vs 对象池)
    • [9. JDK 中的享元模式](#9. JDK 中的享元模式)
      • [Integer 缓存](#Integer 缓存)
    • [10. 典型应用场景](#10. 典型应用场景)
    • [11. 一个常见误区](#11. 一个常见误区)
    • 参考

1. 引言:当对象多到撑爆内存

假设你在做一个文字编辑器,需要显示 100 万个字符:

latex 复制代码
Hello World ...

如果每个字符都 new 一个对象:

  • char
  • 字体
  • 颜色
  • 大小

内存会瞬间爆炸。

但实际上:

"a"和"a"是完全可以共享的。

这正是享元模式的出发点。

当对象多到内存扛不住时,就该考虑享元模式。


2. 什么是享元模式

GoF 定义

运用共享技术有效地支持大量细粒度对象。

详细解释:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。

Flyweight 是"轻量级"的意思,指的是拳击比赛中选手体重的最轻等级。顾名思义,该设计模式的作用是为了让对象更"轻"。

一句话理解:

把可以共享的部分抽出来,让对象变"轻"。


3. 享元模式的核心思想

享元模式的关键是区分两种状态:

状态 含义
内部状态 可以共享,如字符、颜色
外部状态 不可共享,如位置

共享的是内部状态,变化的是外部状态。

  • 内部状态,即不会随着环境的改变而改变的可共享部分。
  • 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

4. 享元模式的结构

包含四个角色:

  1. Flyweight(抽象享元)

通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。

  1. ConcreteFlyweight(具体享元)

它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。

  1. FlyweightFactory(享元工厂)

负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

  1. Unsharable Flyweight(非享元角色)

并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。

  1. Client(客户端)

5. 示例:字符系统

5.1 享元接口

java 复制代码
public interface Character {
    void display(int x, int y);
}

5.2 具体享元

java 复制代码
public class ConcreteCharacter implements Character {

    private char symbol;   // 内部状态

    public ConcreteCharacter(char symbol) {
        this.symbol = symbol;
    }

    @Override
    public void display(int x, int y) {
        System.out.println(symbol + " 显示在 " + x + "," + y);
    }
}

5.3 享元工厂

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class CharacterFactory {

    private static Map<Character, Character> pool = new HashMap<>();

    public static Character get(char c) {
        pool.putIfAbsent(c, new ConcreteCharacter(c));
        return pool.get(c);
    }
}

5.4 客户端使用

java 复制代码
Character c1 = CharacterFactory.get('A');
Character c2 = CharacterFactory.get('A');

System.out.println(c1 == c2); // true

相同字符只创建一次对象。


6. 享元模式的优点

  1. 大幅节省内存
  2. 提高系统性能
  3. 支持海量对象
  4. 对客户端透明

7. 享元模式的缺点

  1. 系统复杂度增加
  2. 需要拆分内外部状态
  3. 调试难度变高

8. 享元 vs 对象池

维度 享元模式 对象池
目标 共享状态 复用对象
是否同时被多个使用
典型 字符、Integer 线程池、连接池

9. JDK 中的享元模式

Integer 缓存

java 复制代码
public class Demo {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;

        System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2));

        Integer i3 = 128;
        Integer i4 = 128;

        System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4));
    }
}

结果如下:

JVM 对 -128 ~ 127 进行缓存,典型享元。

可以看到 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象。


10. 典型应用场景

  • 字符串常量池
  • 数据库连接参数
  • 游戏地图中的格子
  • 图形系统(颜色、字体)

11. 一个常见误区

享元不是为了"减少 new",而是为了"共享状态"。


参考

享元模式 | 菜鸟教程

《图解设计模式》

享元 - Java教程 - 廖雪峰的官方网站

享元设计模式

相关推荐
大猫和小黄2 小时前
若依从零到部署:前后端分离和微服务版
java·微服务·云原生·架构·前后端分离·若依
callJJ2 小时前
Spring设计模式与依赖注入详解
java·spring·设计模式·idea·工厂模式
sxlishaobin2 小时前
设计模式之组合模式
设计模式·组合模式
ExiFengs2 小时前
Java使用策略模式实现多实体通用操作的优雅设计
java·开发语言·设计模式·策略模式
茶本无香2 小时前
设计模式之三—工厂模式:灵活对象创建的艺术
java·开发语言·设计模式·工厂模式
week_泽2 小时前
第7课:管理长期记忆的关键架构决策 - 学习笔记_7
java·笔记·学习·ai agent
爱装代码的小瓶子2 小时前
【c++进阶】c++11下类的新变化以及Lambda函数和封装器
java·开发语言·c++
乌萨奇也要立志学C++2 小时前
【Linux】线程同步 条件变量精讲 + 生产者消费者模型完整实现
java·linux·运维
澄澈青空~2 小时前
病毒木马侵入系统内核的底层运作机理
java·linux·服务器