享元模式详解:解锁高效资源管理的终极武器

🎯 设计模式专栏,持续更新中

欢迎订阅:JAVA实现设计模式

🛠️ 希望小伙伴们一键三连,有问题私信都会回复,或者在评论区直接发言

享元模式

享元模式(Flyweight Pattern) 是一种结构型设计模式,旨在通过共享细粒度对象来减少内存使用和对象创建的开销。享元模式可以在系统中重复使用多个相同或相似的对象,通过避免重复创建相同的对象来提高性能,特别是在大量对象需要频繁创建时,享元模式能够极大减少内存消耗。

核心思想:

享元模式将对象分为内部状态外部状态 ,其中内部状态 可以被共享,而外部状态则由外部提供。通过共享内部状态对象,可以避免创建大量类似的对象。

关键点:

  • 内部状态:可以被共享的状态,不会随着环境改变。
  • 外部状态:根据具体场景变化的状态,通常通过外部传递给享元对象。
  • 享元工厂:用于管理和维护享元对象的共享,确保重复对象不会多次创建。

享元模式的原理类图

  1. FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态的接口或实现
  2. ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
  3. UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
  4. FlyWeightFactory 享元工厂类,用于构建一个池容器(集合), 同时提供从池中获取对象方法

生动的案例:图形绘制系统 🎨

假设我们正在开发一个图形绘制系统 ,需要在屏幕上绘制大量的圆形(Circle)。每个圆形都有颜色、半径、位置等属性。为了提高性能,我们使用享元模式,共享相同颜色的圆形对象,避免重复创建相同颜色的圆形。

  • 共享对象:具有相同颜色的圆形可以共享。
  • 外部状态:圆形的半径和位置(这些信息是具体到每个圆形的,不能共享)

代码实现

Step 1: 创建 Flyweight 接口

定义享元模式的接口,draw() 方法接收外部状态。

java 复制代码
// 享元抽象类
public interface Flyweight {
    void draw(int x, int y, int radius); // 外部状态为位置和半径
}

Step 2: 实现具体的 Flyweight

实现 Flyweight 接口,ConcreteFlyweight 包含共享的颜色属性。

java 复制代码
// 具体享元类
public class ConcreteFlyweight implements Flyweight {
    private final String color; // 共享的内部状态

    public ConcreteFlyweight(String color) {
        this.color = color;
    }

    @Override
    public void draw(int x, int y, int radius) {
        System.out.println("Drawing a " + color + " circle at (" + x + ", " + y + ") with radius " + radius);
    }
}

Step 3: 实现享元工厂类

工厂类负责管理共享的 Flyweight 对象,确保同样颜色的圆形只创建一次。

java 复制代码
public class FlyweightFactory {
    private static final Map<String, Flyweight> flyweights = new HashMap<>();

    public static Flyweight getFlyweight(String color) {
        Flyweight flyweight = flyweights.get(color);
        if (flyweight == null) {
            flyweight = new ConcreteFlyweight(color);
            flyweights.put(color, flyweight);
            System.out.println("Creating a " + color + " flyweight.");
        }
        return flyweight;
    }
    public static int getFlyweightCount(){
        return flyweights.size();
    }
}

Step 4: 客户端使用享元模式

客户端通过工厂获取 Flyweight 对象,并传入外部状态(位置和半径)。

java 复制代码
public class Client {
    public static void main(String[] args) {
        Flyweight circle1 = FlyweightFactory.getFlyweight("Red");
        circle1.draw(10, 10, 5);

        Flyweight circle2 = FlyweightFactory.getFlyweight("Red");
        circle2.draw(20, 20, 10);

        Flyweight circle3 = FlyweightFactory.getFlyweight("Blue");
        circle3.draw(15, 15, 7);

        Flyweight circle4 = FlyweightFactory.getFlyweight("Red");
        circle4.draw(30, 30, 15);
        System.out.println("-------------------------------------");
        System.out.println("Total number of flyweights: " + FlyweightFactory.getFlyweightCount());
    }
}

输出结果

java 复制代码
Creating a Red flyweight.
Drawing a Red circle at (10, 10) with radius 5
Drawing a Red circle at (20, 20) with radius 10
Creating a Blue flyweight.
Drawing a Blue circle at (15, 15) with radius 7
Drawing a Red circle at (30, 30) with radius 15
-------------------------------------
Total number of flyweights: 2

享元模式在源码中的应用

享元模式广泛应用于 Java 标准库和一些优秀的开源框架中,主要用于优化性能、减少内存占用。以下是几个使用了享元模式的经典例子:

1. Java 中的 String 常量池

Java 中的 String 类型是享元模式最典型的应用之一。String 类在 Java 中是不可变的,JVM 会在字符串常量池中缓存相同的字符串对象,以避免重复创建相同内容的字符串,从而节省内存空间。

享元模式的应用

  • 内部状态:字符串内容(相同的字符串内容可以被共享)。
  • 外部状态 :无(因为 String 是不可变的,所有内容都可以共享)

示例代码

java 复制代码
public class StringFlyweightExample {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";

        // s1 和 s2 是同一个对象,因为 JVM 在常量池中共享了 "Hello"
        System.out.println(s1 == s2);  // 输出 true

        // 通过 new 关键字创建新的字符串对象,不会使用常量池
        String s3 = new String("Hello");
        System.out.println(s1 == s3);  // 输出 false
    }
}

JVM 通过字符串常量池优化内存使用,避免重复创建相同内容的字符串。如果想看更深度解读String,请看这篇文章 https://blog.csdn.net/qq_44732500/article/details/141884904

2.Java 中的 Integer 缓存池

Integer 类中也使用了享元模式,对**-128 到 127 之间的整数**进行了缓存。这意味着当我们创建这些范围内的 Integer 对象时,它们将被共享,而不是每次都创建新对象。

享元模式的应用

  • 内部状态:整数值(-128 到 127 之间的整数被缓存共享)。
  • 外部状态:无(这些整数是不可变的)。

源码片段

java 复制代码
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

示例代码

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

        // i1 和 i2 是同一个对象,因为 127 在缓存范围内
        System.out.println(i1 == i2);  // 输出 true

        Integer i3 = Integer.valueOf(128);
        Integer i4 = Integer.valueOf(128);

        // i3 和 i4 不是同一个对象,因为 128 不在缓存范围内
        System.out.println(i3 == i4);  // 输出 false
    }
}

Integer 缓存池 通过共享 -128127 范围内的整数,减少了内存使用。

3.MyBatis 中的 SqlSessionFactory

MyBatis 是一个持久层框架,它通过享元模式优化了 SqlSessionFactory 对象的创建。SqlSessionFactory 是一个重量级对象,通常一个应用只需要一个实例。MyBatis 通过工厂模式和享元模式确保每个数据库只创建一个 SqlSessionFactory 实例,并重复使用。

享元模式的应用:

  • 内部状态SqlSessionFactory 对象(共享的连接配置)。
  • 外部状态 :无(SqlSessionFactory 本身是不可变的)。

示例代码

java 复制代码
public class MyBatisFlyweightExample {
    public static void main(String[] args) {
        SqlSessionFactory sessionFactory1 = MyBatisUtil.getSqlSessionFactory();
        SqlSessionFactory sessionFactory2 = MyBatisUtil.getSqlSessionFactory();

        // sessionFactory1 和 sessionFactory2 是同一个实例
        System.out.println(sessionFactory1 == sessionFactory2);  // 输出 true
    }
}

MyBatis 通过共享 SqlSessionFactory 实现了享元模式,避免重复创建数据库连接工厂,节省了系统资源。

4.Java 数据库连接池

在 Java 应用中,数据库连接池(如 HikariCP、DBCP 等)也是享元模式的经典应用。数据库连接是非常昂贵的资源,连接池通过共享有限的连接对象,避免重复创建和销毁连接,提升了系统性能。

享元模式的应用

  • 内部状态:连接池中的连接对象(可以共享)。
  • 外部状态:连接的具体使用状态(如连接是否空闲)。

代码概念

java 复制代码
// 数据库连接池 Flyweight 模式
HikariDataSource dataSource = new HikariDataSource();
Connection conn1 = dataSource.getConnection();
Connection conn2 = dataSource.getConnection();

// 使用同一个数据库连接池管理连接对象

数据库连接池通过享元模式复用连接对象,大大提高了数据库连接的性能和资源管理。

总结

  1. 减少内存消耗:通过共享相同对象,减少重复对象的创建,特别适合大量相似对象的场景。
  2. 提高系统性能:通过共享对象,减少内存使用,提升程序的运行效率。
  3. 增加系统复杂性:为了区分内部状态和外部状态,系统设计可能会更加复杂
  4. 适用场景有限:享元模式适用于存在大量相同或相似对象的场景,如果对象的状态不易共享,享元模式的效果有限。

使用场景

  1. 需要大量重复对象的场景:如图形绘制系统、文本编辑器中字符对象的管理。
  2. 缓存池:享元模式常用于对象池或缓存池中,避免创建大量相同的对象。
  3. 游戏开发:在游戏中,地图上的草地、树木等对象可以使用享元模式进行共享。
相关推荐
duration~34 分钟前
Maven随笔
java·maven
zmgst38 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵1 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong2 小时前
Java反射
java·开发语言·反射
狂放不羁霸2 小时前
idea | 搭建 SpringBoot 项目之配置 Maven
spring boot·maven·intellij-idea
九圣残炎2 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge2 小时前
Netty篇(入门编程)
java·linux·服务器
计算机学长felix3 小时前
基于SpringBoot的“校园交友网站”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·毕业设计·交友