设计模式--享元模式:优化内存使用的轻量级设计

享元模式:优化内存使用的轻量级设计

今天我们来深入探讨享元模式(Flyweight Pattern),一种结构型设计模式,用于通过共享对象来减少内存使用和提高性能。享元模式将对象的状态分为内在状态(共享)和外在状态(不共享),适用于需要创建大量相似对象的场景。本文将带你实现一个简单的享元模式示例,适合初学者快速上手,同时为有经验的开发者提供进阶建议和优化思路。

享元模式在现实生活中类似共享单车,多个用户共享同一辆车,减少资源浪费。本文使用 Java 语言,通过一个文本渲染系统的场景展示享元模式的实现。让我们开始吧!

前置准备

在开始之前,确保开发环境已就绪:

  • JDK:推荐 JDK 17(也可使用 JDK 8+)。

  • IDE:IntelliJ IDEA、Eclipse 或 VS Code,推荐支持 Java 的 IDE。

  • 构建工具:Maven(可选,用于管理依赖)。

  • 项目结构 :创建一个简单的 Java 项目,目录如下:

    复制代码
    flyweight-pattern-demo
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── com.example.flyweight
    │   │   │       ├── flyweight
    │   │   │       ├── factory
    │   │   │       └── Main.java
    │   └── test
    └── pom.xml

安装环境

  • 确保 JDK 已安装:java -version.
  • Maven(可选):mvn -version.
  • 无需额外依赖,本示例使用纯 Java。

步骤 1: 定义享元接口

享元模式需要一个享元接口,定义共享对象的操作。在 com.example.flyweight.flyweight.Character 中:

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

public interface Character {
    void render(String extrinsicState);
}

说明

  • Character 是享元接口,定义字符渲染行为,extrinsicState 表示外在状态(如位置)。

步骤 2: 创建具体享元类

实现具体的字符类,存储内在状态(共享)。在 com.example.flyweight.flyweight.ConcreteCharacter 中:

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

public class ConcreteCharacter implements Character {
    private final char symbol; // 内在状态,共享
    private final String font; // 内在状态,共享

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

    @Override
    public void render(String extrinsicState) {
        System.out.println("Rendering character '" + symbol + "' in font '" + font + "' at position: " + extrinsicState);
    }
}

说明

  • ConcreteCharacter 存储内在状态(如字符和字体),通过 render 方法结合外在状态(位置)进行渲染。

步骤 3: 创建享元工厂

实现享元工厂,管理共享对象。在 com.example.flyweight.factory.CharacterFactory 中:

java 复制代码
package com.example.flyweight.factory;

import com.example.flyweight.flyweight.Character;
import com.example.flyweight.flyweight.ConcreteCharacter;

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

public class CharacterFactory {
    private final Map<String, Character> characters = new HashMap<>();

    public Character getCharacter(char symbol, String font) {
        String key = symbol + "_" + font;
        Character character = characters.get(key);
        if (character == null) {
            character = new ConcreteCharacter(symbol, font);
            characters.put(key, character);
            System.out.println("Created new character: " + key);
        }
        return character;
    }
}

说明

  • CharacterFactory 使用 HashMap 缓存共享对象,避免重复创建。
  • key 基于内在状态(字符和字体)生成。

步骤 4: 客户端代码

com.example.flyweight.Main 中测试享元模式:

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

import com.example.flyweight.factory.CharacterFactory;
import com.example.flyweight.flyweight.Character;

public class Main {
    public static void main(String[] args) {
        CharacterFactory factory = new CharacterFactory();

        // 渲染多个字符,复用相同的享元对象
        Character charA1 = factory.getCharacter('A', "Arial");
        charA1.render("Position (10, 20)");

        Character charA2 = factory.getCharacter('A', "Arial");
        charA2.render("Position (30, 40)");

        Character charB = factory.getCharacter('B', "Times");
        charB.render("Position (50, 60)");
    }
}

运行输出

复制代码
Created new character: A_Arial
Rendering character 'A' in font 'Arial' at position: Position (10, 20)
Rendering character 'A' in font 'Arial' at position: Position (30, 40)
Created new character: B_Times
Rendering character 'B' in font 'Times' at position: Position (50, 60)

步骤 5: 运行和测试

  1. 编译和运行

    • 在 IDE 中运行 Main 类。

    • 或使用命令行:

      bash 复制代码
      javac src/main/java/com/example/flyweight/*.java src/main/java/com/example/flyweight/*/*.java
      java com.example.flyweight.Main
  2. 测试用例

    • 验证相同字符和字体的对象只创建一次(复用)。
    • 验证不同字符或字体创建新对象。
    • 检查渲染输出是否正确包含外在状态。
  3. 调试技巧

    • 添加日志:使用 System.out 或 SLF4J 记录对象创建和复用。
    • 检查缓存:在调试器中验证 characters 映射的大小。
    • 异常处理:检查无效输入(如空字体)。

进阶与最佳实践

  • 线程安全

    • 确保工厂线程安全:

      java 复制代码
      private final Map<String, Character> characters = new ConcurrentHashMap<>();
      
      public synchronized Character getCharacter(char symbol, String font) {
          String key = symbol + "_" + font;
          return characters.computeIfAbsent(key, k -> {
              System.out.println("Created new character: " + k);
              return new ConcreteCharacter(symbol, font);
          });
      }
  • 扩展享元

    • 添加更多内在状态(如字体大小):

      java 复制代码
      public class ConcreteCharacter implements Character {
          private final char symbol;
          private final String font;
          private final int fontSize;
      
          public ConcreteCharacter(char symbol, String font, int fontSize) {
              this.symbol = symbol;
              this.font = font;
              this.fontSize = fontSize;
          }
      
          @Override
          public void render(String extrinsicState) {
              System.out.println("Rendering character '" + symbol + "' in font '" + font + "' size " + fontSize + " at position: " + extrinsicState);
          }
      }
  • 异常处理

    • 添加输入验证:

      java 复制代码
      public Character getCharacter(char symbol, String font) {
          if (font == null || font.isEmpty()) {
              throw new IllegalArgumentException("Font cannot be empty");
          }
          // ...
      }
  • 测试

    • 使用 JUnit 编写单元测试:

      java 复制代码
      import org.junit.Test;
      import static org.junit.Assert.*;
      
      public class CharacterFactoryTest {
          @Test
          public void testCharacterReuse() {
              CharacterFactory factory = new CharacterFactory();
              Character char1 = factory.getCharacter('A', "Arial");
              Character char2 = factory.getCharacter('A', "Arial");
              assertSame("Characters should be the same instance", char1, char2);
          }
      }
  • 其他应用场景

    • 游戏开发:共享纹理或模型对象。
    • 文本编辑器:复用字符或图形对象。
    • 缓存系统:共享不可变数据。
  • 资源推荐:书籍《设计模式:可复用面向对象软件的基础》、Refactoring Guru 网站。多实践其他设计模式(如组合模式、代理模式)。

总结

通过这个享元模式示例,你学会了如何通过共享对象减少内存使用,实现了文本渲染系统的优化设计。享元模式在需要创建大量相似对象时非常实用,广泛应用于游戏开发、文本处理和缓存系统。

相关推荐
2301_803554523 小时前
C++联合体(Union)详解:与结构体的区别、联系与深度解析
java·c++·算法
EnCi Zheng4 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端
烙印6014 小时前
Spring容器的心脏:深度解析refresh()方法(上)
java·后端·spring
为什么我不是源代码4 小时前
JPA读取数据库离谱问题-No property ‘selectClassByName‘ found-Not a managed type
java·sql
Lisonseekpan4 小时前
Guava Cache 高性能本地缓存库详解与使用案例
java·spring boot·后端·缓存·guava
我真的是大笨蛋4 小时前
Redis的String详解
java·数据库·spring boot·redis·spring·缓存
心态特好4 小时前
Jwt非对称加密的应用场景
java
bkspiderx4 小时前
C++设计模式之行为型模式:中介者模式(Mediator)
c++·设计模式·中介者模式
敢敢J的憨憨L5 小时前
GPTL(General Purpose Timing Library)使用教程
java·服务器·前端·c++·轻量级计时工具库