设计模式之享元模式

一、介绍

享元模式(FlyWeight),属于结构型设计模式,主要解决实例化大量相同的对象,从而导致可能的内存泄漏的问题。

为了解决这个问题,享元模式提出的解决办法是将相同的对象保存在内存中,且仅保存一个对象,因此该对象应该是不可被修改的,当需要获取该对象实例时,直接从内存中读取即可,从而避免了相同对象的重复创建。

下面是享元模式的定义:

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

二、主要角色

享元模式有以下四个基本角色:

享元工厂(FlyWeightFactory)

工厂模式的应用,用来创建并管理享元对象,根据某一标准,在完成享元对象实例化以后,判断是否需要对该实例对象进行管理。如果客户端需要从享元工厂中获取某些实例,享元工厂将会判断是否对相同的实例进行管理,如果存在被管理的相同的实例对象,则无需重复对其进行实例化,而是直接返回被管理的对象。

一般来说,享元工厂通过HashMap对需要共享的实例进行管理。

抽象享元(FlyWeight)

享元类的抽象接口,规定了对象的行为。

共享的具体享元(SharedFlyWeightImpl)

实现于抽象享元接口,对其规定的行为进行具体实现。并且该类的实例对象在由享元工厂实例化以后,需要被工厂管理,以便在以后需要相同的对象时直接返回,而不是重复实例化。

非共享的具体享元(UnsharedFlyWeightImpl)

实现于抽象享元接口,对其规定的行为进行具体实现。与共享的具体享元实例不同的是,非共享的具体享元在被享元工厂实例化以后,不被工厂管理,即每一次需要该实例时,都需要享元工厂重复创建

三、基本原理

享元模式的核心思想是分离出不变部分与可变部分,通过共享不变部分来减少对象的创建,从而降低内存消耗和提高性能。这种模式的实现方式是通过将对象的状态分为内部状态和外部状态,内部状态是对象不可变的共享部分,而外部状态是可以改变的独立部分。当多个客户端请求同一个享元对象时,它们实际上共享的是同一个对象,这样就避免了重复创建相同的对象,从而节省了内存空间。同时,享元模式还通过工厂模式来实现对象的创建和管理,通过缓存等技术手段来提高对象的复用效率

四、案例

案例背景:实现一个文本编辑器的基础字符显示功能,要求支持大量字符(如字母、数字)的显示,每个字符需记录其编码(如'A'、'1')和位置(行号、列号)

传统实现(痛点版)

java 复制代码
public class Test1 {
    public static void main(String[] args) {
        // 使用示例:创建10000个'A'字符
        List<Character> characters = new ArrayList<>();
        // 创建10000个'A',每个都是独立对象(内存浪费)
        for (int i = 0; i < 10000; i++) {
            characters.add(new Character("A", i / 100, i % 100));
        }
        // 显示所有字符
        characters.forEach(Character::display);

    }


    // 传统实现:每个字符独立创建对象
    static class Character {
        private String code;  // 字符编码(如'A')
        private int row;      // 行号(外部状态)
        private int col;      // 列号(外部状态)

        public Character(String code, int row, int col) {
            this.code = code;
            this.row = row;
            this.col = col;
        }

        public void display() {
            System.out.printf("字符:%s,位置:(%d,%d)%n", code, row, col);
        }
    }
}

痛点总结:

  • 内存浪费严重:10000个'A'字符被创建为10000个独立对象,但它们的code字段完全相同(仅row和col不同)。
  • 性能低下:大量对象创建/销毁增加GC压力,尤其在高频操作(如文本输入)时可能导致卡顿。
  • 管理复杂:无法统一控制字符对象的生命周期(如批量回收或修改共享属性)

享元模式 实现(优雅版)

抽象享元角色:定义字符的公共接口

java 复制代码
public interface FlyweightCharacter {
    void display(int row, int col);  // 显示方法(接收外部状态:行、列)
}

具体享元角色:实现字符的共享逻辑(内部状态:code)

java 复制代码
public class ConcreteCharacter implements FlyweightCharacter {
    private String code ;  // 内部状态
    @Override
    public void display(int row, int col) {
        System.out.printf("字符:%s,位置:(%d,%d)%n", code, row, col);
    }
}

享元工厂:管理字符对象的共享(单例模式)

java 复制代码
public class CharacterFactory {
    private static final CharacterFactory INSTANCE = new CharacterFactory();
    private final Map<String, FlyweightCharacter> pool = new HashMap<>();  // 缓存池(code→字符对象)

    private CharacterFactory() {}  // 私有构造

    public static CharacterFactory getInstance() {
        return INSTANCE;
    }

    // 获取或创建字符对象(关键:仅当code不存在时新建)
    public FlyweightCharacter getCharacter(String code) {
        if (!pool.containsKey(code)) {
            pool.put(code, new ConcreteCharacter(code));  // 仅创建一次
        }
        return pool.get(code);
    }

    // 统计缓存大小(用于验证共享效果)
    public int poolSize() {
        return pool.size();
    }
}

测试

五、优缺点

优点

  • 减少内存消耗:通过共享对象,避免创建大量重复的对象实例,从而减少内存消耗。
  • 提高性能:由于对象被共享,因此可以减少对象的创建和销毁,从而提高系统的性能。
  • 外部状态与内部状态的分离:享元模式将对象的外部状态和内部状态分离,使得对象的状态更加灵活和可维护。

缺点

  • 复杂度较高:享元模式需要分离出内部状态和外部状态,这使得程序的逻辑变得复杂,设计不当可能导致逻辑混乱
  • 运行时间可能变长:为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
  • 需要维护享元池:享元模式需要维护一个享元池来管理共享对象,这可能会引入额外的复杂性
  • 线程安全问题 若工厂类非线程安全(如使用普通HashMap),多线程并发获取对象时可能导致数据不一致(可通过ConcurrentHashMap解决)

六、总结

总之,享元模式适用于那些需要大量使用相同或相似对象,并且需要频繁创建和销毁同一类对象的场景。通过共享对象来避免重复创建相同的对象,从而提高系统性能和降低内存消耗,但是享元模式也存在一些缺点,需要根据具体情况权衡其优缺点,考虑是否适合使用享元模式。

相关推荐
北辰当尹3 分钟前
【实习之旅】Kali虚拟机桥接模式ping通百度
java·服务器·桥接模式
Just Dreamchaser8 分钟前
Pdf和Docx文件导出生成水印工具类
java·给pdf和docx文件添加水印
这个需求做不了10 分钟前
Java实现文件格式转换(图片,视频,文档,音频)
java
愿你天黑有灯下雨有伞15 分钟前
高性能Java并发编程:如何优雅地使用CompletableFuture进行异步编排
java
indexsunny16 分钟前
互联网大厂Java面试实战:基于电商场景的Spring Boot与微服务技术问答
java·spring boot·微服务·面试·hibernate·电商场景·技术问答
qq_124987075317 分钟前
基于Spring Boot的电影票网上购票系统的设计与实现(源码+论文+部署+安装)
java·大数据·spring boot·后端·spring·毕业设计·计算机毕业设计
无心水18 分钟前
【分布式利器:腾讯TSF】6、TSF可观测性体系建设实战:Java全链路Metrics+Tracing+Logging落地
java·分布式·架构·wpf·分布式利器·腾讯tsf·分布式利器:腾讯tsf
小鸡脚来咯21 分钟前
Java字符串详解
java·开发语言
麦兜*21 分钟前
【Spring Boot】 接口性能优化“十板斧”:从数据库连接到 JVM 调优的全链路提升
java·大数据·数据库·spring boot·后端·spring cloud·性能优化
廋到被风吹走28 分钟前
【Spring 】Spring Security深度解析:过滤器链、认证授权架构与现代集成方案
java·spring·架构