23种设计模式之【享元模式】-核心原理与 Java实践

文章目录

享元模式(Flyweight Pattern)

享元模式是 23 种设计模式中的一种结构型模式,其核心思想是通过共享技术复用大量细粒度的相似对象,减少对象创建的数量,从而降低内存占用和提高系统性能。这种模式将对象的状态分为可共享的 "内在状态" 和不可共享的 "外在状态",通过共享内在状态来实现对象复用。

核心原理

  • 抽象享元(Flyweight):
    定义享元对象的接口,声明对外在状态的操作方法
    内在状态作为对象的成员变量存储,外在状态通过方法参数传入
  • 具体享元(ConcreteFlyweight):
    实现抽象享元接口,存储可共享的内在状态
    对外在状态进行处理,但不存储外在状态
  • 享元工厂(FlyweightFactory):
    负责创建和管理享元对象,确保合理共享
    维护一个享元池(如哈希表),当请求享元时,优先从池中获取
  • 客户端(Client):
    负责维护享元对象的外在状态
    通过享元工厂获取享元对象,并为其设置外在状态
    享元模式的核心是 "分离内在状态与外在状态,共享内在状态",通过减少对象数量来优化系统资源消耗。

Java 实践示例

以 "文字处理系统" 为例实现享元模式:

文档中存在大量重复字符(如字母、数字),字符的字形(字体、大小)是可共享的内在状态

字符在文档中的位置(x,y 坐标)是不可共享的外在状态

通过享元模式共享相同字符对象,只存储一次字形信息,减少内存占用

java 复制代码
package com.example.demo;

import java.util.HashMap;
import java.util.Map;

public class FlyweightPattern {

    public static void main(String[] args) {
        // 创建享元工厂
        CharacterFactory factory = new CharacterFactory();

        // 获取并使用享元对象
        CharacterFlyweight c1 = factory.getCharacter('A');
        c1.display(10, 20); // 位置(10,20)

        CharacterFlyweight c2 = factory.getCharacter('B');
        c2.display(30, 40); // 位置(30,40)

        CharacterFlyweight c3 = factory.getCharacter('A');
        c3.display(50, 60); // 位置(50,60)

        // 验证是否共享了相同对象
        System.out.println("\n字符'A'的两个实例是否为同一对象:" + (c1 == c3));
        System.out.println("工厂中缓存的享元数量:" + factory.getFlyweightCount());

        //创建新的字符享元:A
        //显示字符 'A' (字体:Arial, 字号:12) 在位置 (10,20)
        //创建新的字符享元:B
        //显示字符 'B' (字体:Arial, 字号:12) 在位置 (30,40)
        //复用已有的字符享元:A
        //显示字符 'A' (字体:Arial, 字号:12) 在位置 (50,60)
        //
        //字符'A'的两个实例是否为同一对象:true
        //工厂中缓存的享元数量:2
    }
    // 抽象享元:字符接口
    public interface CharacterFlyweight {
        // 显示字符,x和y是外在状态(位置)
        void display(int x, int y);
    }

    // 具体享元:字符实现类
    public static class ConcreteCharacter implements CharacterFlyweight {
        // 内在状态:字符值(可共享)
        private char c;
        // 内在状态:字体(可共享)
        private String font;
        // 内在状态:字号(可共享)
        private int size;

        // 构造函数初始化内在状态
        public ConcreteCharacter(char c) {
            this.c = c;
            // 模拟默认字体和字号(实际中可通过参数传入)
            this.font = "Arial";
            this.size = 12;
        }

        @Override
        public void display(int x, int y) {
            // 外在状态(x,y)由客户端传入,不存储在享元中
            System.out.printf("显示字符 '%c' (字体:%s, 字号:%d) 在位置 (%d,%d)\n",
                    c, font, size, x, y);
        }
    }

    public static class CharacterFactory {
        // 享元池:缓存已创建的享元对象
        private Map<Character, CharacterFlyweight> flyweightPool = new HashMap<>();

        // 获取享元对象:存在则从池获取,不存在则创建并缓存
        public CharacterFlyweight getCharacter(char c) {
            // 检查池中是否存在
            if (!flyweightPool.containsKey(c)) {
                // 不存在则创建新的享元对象
                flyweightPool.put(c, new ConcreteCharacter(c));
                System.out.println("创建新的字符享元:" + c);
            } else {
                System.out.println("复用已有的字符享元:" + c);
            }
            return flyweightPool.get(c);
        }

        // 获取缓存的享元数量
        public int getFlyweightCount() {
            return flyweightPool.size();
        }
    }
}

享元模式的关键概念

内在状态(Intrinsic State):

存储在享元对象内部,可被多个对象共享的状态

不随环境变化,例如示例中的字符值、字体、字号

在享元对象创建时初始化,之后不再改变

外在状态(Extrinsic State):

随环境变化,不能被共享的状态

由客户端维护,在使用享元时通过方法参数传入

例如示例中的字符位置(x,y 坐标)

享元模式的特点

优点:

减少对象数量:通过共享相似对象,显著降低内存占用

提高性能:减少对象创建和垃圾回收的开销

分离内在与外在状态:使享元对象更专注于核心功能

缺点:

增加系统复杂度:需要分离状态并管理享元池

外在状态处理成本:可能需要客户端维护复杂的外在状态

适用条件:

系统中存在大量相似对象,且创建成本高

对象的大部分状态可以外部化

存在访问这些对象的集中点(便于使用工厂管理)

享元模式的应用场景

字符串常量池:

Java 中的String常量池是享元模式的经典实现

相同字符串字面量会被共享,如"abc" == "abc"返回true

数据库连接池:

连接池缓存数据库连接对象,避免频繁创建和关闭连接

连接的用户名、密码等是内在状态,每次查询参数是外在状态

游戏开发:

大量相似游戏元素(如树木、士兵、粒子)共享模型数据

位置、朝向等是外在状态,模型、纹理等是内在状态

GUI 组件:

相同样式的按钮、标签等组件共享渲染信息

位置、大小等是外在状态,颜色、字体等是内在状态

缓存框架:

如 Redis、EhCache 等缓存框架,通过共享缓存对象提高访问速度

享元模式是优化系统资源的重要手段,尤其适合处理大量细粒度相似对象的场景。其核心在于合理划分内在状态与外在状态,通过享元工厂实现对象的高效复用,从而在内存占用和性能之间取得平衡。

相关推荐
不搞学术柒柒2 小时前
设计模式-常见设计原则篇
设计模式
_extraordinary_2 小时前
Java Servlet(一)--- Servlet hello world的写法,smart tomcat,Servlet代码中的常见问题
java·servlet·tomcat
智界软体库3 小时前
《IDEA 2025长效使用配置指南:有效期配置至2099年实战之JetBrains全家桶有效》
java·ide·intellij-idea
Yang.O3 小时前
MyEclipse在高分辨率显示屏上图标显示太小的解决方案
java·ide·myeclipse
刘火锅4 小时前
Java读取Excel图片技术详解:悬浮式与嵌入式图片的三种实现方案(支持WPS嵌入和Office Excel嵌入)
java·excel·wps
神云瑟瑟4 小时前
spring boot拦截器获取requestBody的巨坑
java·spring boot·拦截器
博睿谷IT99_4 小时前
Linux 备份与恢复常用命令
java·linux·服务器
qq_402605654 小时前
JAVA大文件分片上传
java·大文件上传