【西瓜带你学设计模式 | 第十四期 - 享元模式】享元模式 —— 内外状态分离与对象共享实现、优缺点与适用场景

文章目录

    • 前言
    • [1. 享元模式是什么?](#1. 享元模式是什么?)
    • [2. 享元模式解决什么问题?](#2. 享元模式解决什么问题?)
    • [3. 核心结构](#3. 核心结构)
      • [3.1 Flyweight(抽象享元角色)](#3.1 Flyweight(抽象享元角色))
      • [3.2 ConcreteFlyweight(具体享元角色)](#3.2 ConcreteFlyweight(具体享元角色))
      • [3.3 FlyweightFactory(享元工厂 / 享元池)](#3.3 FlyweightFactory(享元工厂 / 享元池))
      • [3.4 Client(客户端)](#3.4 Client(客户端))
    • [4. 实现思路](#4. 实现思路)
    • [5. 示例](#5. 示例)
      • [5.1 Flyweight:字符接口(共享对象)](#5.1 Flyweight:字符接口(共享对象))
      • [5.2 ConcreteFlyweight:具体字符(被复用对象)](#5.2 ConcreteFlyweight:具体字符(被复用对象))
      • [5.3 FlyweightFactory:享元工厂/缓存池](#5.3 FlyweightFactory:享元工厂/缓存池)
      • [5.4 Client:客户端调用(外部状态随调用变化)](#5.4 Client:客户端调用(外部状态随调用变化))
    • [6. 优缺点](#6. 优缺点)
      • [6.1 优点](#6.1 优点)
      • [6.2 缺点](#6.2 缺点)
    • [7. 和其他模式怎么区分?](#7. 和其他模式怎么区分?)
      • [7.1 享元 vs 单例](#7.1 享元 vs 单例)
      • [7.2 享元 vs 代理](#7.2 享元 vs 代理)
      • [7.3 享元 vs 组合](#7.3 享元 vs 组合)
    • [8. 适用场景](#8. 适用场景)
    • [9. 总结](#9. 总结)

前言

在很多系统里,我们都会遇到同一个现象:

  • 同类对象会被创建得很多(例如:相同字符、相同颜色、相同提示文本、相同菜单项...)
  • 但它们的大部分状态是相同的,只是少部分状态(比如"位置、上下文、时间、用户态信息")会变化
  • 对象创建和内存占用不断上涨,最后变成性能和成本问题

享元模式(Flyweight Pattern) 想解决的核心就是:

尽可能复用"已经创建过的对象",把重复的东西共享起来,只保留真正变化的部分给外部。


1. 享元模式是什么?

享元模式是一种结构型设计模式,通过"共享对象"的方式减少内存消耗。

关键点是:把对象拆成两类状态:

  • 内部状态(Intrinsic State):不随外部变化,适合共享(通常是固有的、可缓存的)
  • 外部状态(Extrinsic State):随上下文变化,不适合共享(例如:位置、渲染参数、用户信息等)

把相同的对象"尽量少创建",用 Key 找到已有对象复用;变化的部分由外部传入。


2. 享元模式解决什么问题?

  1. 应用里大量重复对象,数量可能非常大
  2. 重复对象中一部分状态相同
  3. 创建这些对象的成本(内存/时间)很高
  4. 能把"对象的**可变部分"**从对象内部抽出去

如果你把所有状态都放在对象里,那就很难复用了------因为每个对象都会"独一无二"。


3. 核心结构

享元模式常见的结构如下:

3.1 Flyweight(抽象享元角色)

定义共享对象需要实现的行为,并且接受外部状态参数

内部状态在享元对象中缓存;外部状态在调用时传入。

3.2 ConcreteFlyweight(具体享元角色)

真正被缓存复用的对象类型。内部通常会存:

  • 内部状态(Intrinsic State)
  • 行为逻辑:使用内部状态 + 外部状态完成工作

3.3 FlyweightFactory(享元工厂 / 享元池)

负责"创建/获取享元":

  • 对某个 Key(例如颜色值、字符值、样式ID)先查缓存
  • 有就直接复用
  • 没有就创建并放入缓存

3.4 Client(客户端)

客户端不直接 new 具体享元,而是:

  • 用外部参数 + 内部Key 去工厂获取享元
  • 然后把外部状态传给享元执行

4. 实现思路

实现时通常为把"可变状态"挪出去:

  1. 抽象一个 Flyweight 接口(方法里带外部状态参数)
  2. 写具体享元,缓存内部状态(只存可共享的部分)
  3. 做一个工厂(Map/缓存池)用 Key 管理享元
  4. 客户端通过工厂拿享元,再传外部状态执行

5. 示例

终端/画布上会画很多字符,但字符本身(内部状态)只有类型不同;位置等(外部状态)每次都不同。

  • 内部状态 :字符本身(char
  • 外部状态 :绘制位置(x, y

5.1 Flyweight:字符接口(共享对象)

java 复制代码
public interface Glyph {
    void draw(int x, int y); // x,y 是外部状态
}

5.2 ConcreteFlyweight:具体字符(被复用对象)

java 复制代码
public class ConcreteGlyph implements Glyph {
    private final char ch; // 内部状态:字符本身(可共享)

    public ConcreteGlyph(char ch) {
        this.ch = ch;
    }

    @Override
    public void draw(int x, int y) {
        System.out.println("绘制字符 '" + ch + "' 到 (" + x + ", " + y + ")");
    }
}

5.3 FlyweightFactory:享元工厂/缓存池

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

public class GlyphFactory {
    private final Map<Character, Glyph> cache = new HashMap<>();

    public Glyph getGlyph(char ch) {
        // 内部状态的 Key:字符 char
        return cache.computeIfAbsent(ch, k -> new ConcreteGlyph(k));
    }
}

5.4 Client:客户端调用(外部状态随调用变化)

java 复制代码
public class Client {
    public static void main(String[] args) {
        GlyphFactory factory = new GlyphFactory();

        Glyph a = factory.getGlyph('A');
        a.draw(10, 20);

        Glyph a2 = factory.getGlyph('A');
        a2.draw(100, 200);

        Glyph b = factory.getGlyph('B');
        b.draw(30, 40);
    }
}

会发现:

  • 'A' 只会创建一次(后续复用)
  • draw(x,y) 每次都带不同位置(外部状态)

6. 优缺点

6.1 优点

  1. 显著减少对象数量,降低内存占用
  2. 减少创建开销,提升性能
  3. 对于"重复多、共享可能高"的场景非常有效

6.2 缺点

  1. 拆分内部/外部状态增加复杂度
  2. 客户端必须传入外部状态,接口设计要更谨慎
  3. 享元池可能带来管理成本(缓存、过期策略等)
  4. 如果内部状态设计不好,复用率会很低,甚至得不偿失

7. 和其他模式怎么区分?

7.1 享元 vs 单例

  • 单例:保证"一个实例"
  • 享元:保证"同一类/同一 Key 的实例尽量少",可能有多个(按 Key 缓存)

7.2 享元 vs 代理

  • 代理:控制访问/延迟加载/权限等
  • 享元:共享对象本体,减少重复创建与内存占用

7.3 享元 vs 组合

  • 组合:树形结构统一处理
  • 享元:共享对象状态减少资源消耗

8. 适用场景

  • 大量对象可归类,且存在大量可共享部分
  • 内部状态相对稳定,外部状态在运行中变化
  • 典型如:文本编辑/渲染、图形绘制(颜色、字体、样式)、缓存规则对象、棋盘/网格格子等

如果发现对象里"重复字段很多",而"真正每次变化的字段很少",那可以使用享元模式


9. 总结

享元模式通过区分内部状态与外部状态,把大量可共享对象放入享元池复用;客户端从工厂按 Key 获取享元,再把变化的外部状态传入执行,从而显著减少内存与创建开销。

相关推荐
小怪吴吴11 分钟前
idea 开发Android
android·java·intellij-idea
嘻嘻哈哈樱桃13 分钟前
牛客经典101题题解集--动态规划
java·数据结构·python·算法·职场和发展·动态规划
一次旅行15 分钟前
IDEA安装CC GUI新手指南
java·ide·intellij-idea
超梦dasgg19 分钟前
Spring AI 智能航空助手项目实战
java·人工智能·后端·spring·ai编程
counting money1 小时前
Spring框架基础(配置篇)
java·后端·spring
秋92 小时前
OceanBase与GreatSQL在Java应用中的性能调优方法有哪些?
java·开发语言·oceanbase
今天又在写代码2 小时前
并发问题解决
java·开发语言·数据库
老王以为2 小时前
前端视角下的 Java
java·javascript·程序员
看腻了那片水2 小时前
开源一个对业务代码零侵入的透明数据治理框架 —— 【sangsang】
java·mybatis