【设计模式系列笔记】享元模式

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来最小化内存使用或计算开销,以提高性能。该模式适用于需要大量相似对象的场景,通过共享这些相似对象的部分状态,可以有效减少内存消耗。

1. 享元模式介绍

享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来最小化内存使用或计算开销,以提高性能。该模式适用于需要大量相似对象的场景,通过共享这些相似对象的部分状态,可以有效减少内存消耗。

2. 关键思想

享元模式的关键思想是通过共享来减少对象的数量,以降低内存消耗和提高性能。具体而言,享元模式包含以下关键思想:

  1. 内部状态和外部状态的分离: 将对象的状态分为内部状态(Intrinsic State)和外部状态(Extrinsic State)。内部状态是可以被多个对象共享的部分,而外部状态是对象的变化部分,每个对象都有自己的外部状态。
  2. 共享内部状态: 享元模式的核心是共享相似对象的内部状态。通过将相似对象的内部状态抽取出来,可以在多个对象之间共享这部分状态,从而减少内存占用。
  3. 享元工厂的管理: 引入一个享元工厂,负责创建和管理享元对象。工厂维护一个享元池,确保相似对象被共享,而不是每次都创建新的对象。
  4. 外部状态的外部管理: 对于不可共享的外部状态,由客户端负责传递给享元对象。这样,享元对象可以在操作时使用外部状态,而不需要存储它,从而进一步减小对象的存储开销。
  5. 提高性能和减少内存占用: 通过共享内部状态,可以减少对象的数量,降低内存占用,提高系统性能。尤其在需要大量相似对象的场景下,享元模式可以显著减少系统资源的消耗。

总体而言,享元模式适用于需要创建大量相似对象,并且这些对象的大部分状态是可以共享的情况。通过合理地管理内部状态和外部状态,可以有效地优化系统的性能和内存使用。

3. 实现方式:

假设我们要实现一个简单的文本编辑器,其中可能包含大量相似的字符,比如相同的字母。这时候我们可以考虑使用享元模式来减少内存占用。

// 字符享元接口
interface CharFlyweight {
    void display();
}
// 具体字符享元类
class ConcreteCharFlyweight implements CharFlyweight {
    private char intrinsicState; // 内部状态(属性),可以被共享
    // 构造方法,初始化内部状态
    public ConcreteCharFlyweight(char intrinsicState) {
        this.intrinsicState = intrinsicState;
    }
    // 实现显示方法,用于展示字符的内部状态
    @Override
    public void display() {
        System.out.print(intrinsicState);
    }
}
// 字符享元工厂类
class CharFlyweightFactory {
    // 存储已创建的字符享元对象,使用 Map 保证快速查找
    private static final Map<Character, CharFlyweight> charFlyweights = new HashMap<>();
    // 获取字符享元对象,如果不存在则创建并放入享元池
    public static CharFlyweight getCharFlyweight(char key) {
        // 检查是否已存在该字符的享元对象
        if (!charFlyweights.containsKey(key)) {
            // 如果不存在,则创建新的字符享元对象并加入享元池
            charFlyweights.put(key, new ConcreteCharFlyweight(key));
        }
        // 返回字符的享元对象
        return charFlyweights.get(key);
    }
}
// 客户端使用享元模式
public class TextEditorClient {
    public static void main(String[] args) {
        // 待处理的文本内容
        String text = "Hello, World!";
        // 遍历文本,获取并显示每个字符的享元对象
        for (char c : text.toCharArray()) {
            // 获取字符的享元对象
            CharFlyweight charFlyweight = CharFlyweightFactory.getCharFlyweight(c);
            
            // 显示字符的内部状态
            charFlyweight.display();
        }
    }
}

在这个例子中,每个字符被看作是一个享元对象。CharFlyweightFactory 作为享元工厂,负责管理字符的享元对象。当客户端在文本中遇到一个字符时,通过享元工厂获取对应的享元对象,确保相同字符实际上是同一个对象。这样,对于大量相似的字符,我们只需要维护不同字符的享元对象,而不是为每个字符都创建一个新的对象,从而节省了内存。

要点:

  1. 内部状态和外部状态分离: 关键思想是将对象的状态分为内部状态和外部状态。内部状态是可以被多个对象共享的,而外部状态是对象的变化部分,由客户端在使用时传递。
  2. 共享内部状态: 享元模式的核心是共享相似对象的内部状态。通过将相似对象的内部状态抽取出来,可以在多个对象之间共享这部分状态,从而减少内存占用。
  3. 享元工厂的管理: 引入一个享元工厂,负责创建和管理享元对象。工厂维护一个享元池,确保相似对象被共享,而不是每次都创建新的对象。
  4. 外部状态的外部管理: 对于不可共享的外部状态,由客户端负责传递给享元对象。这样,享元对象可以在操作时使用外部状态,而不需要存储它,从而进一步减小对象的存储开销。
  5. 提高性能和减少内存占用: 通过共享内部状态,可以减少对象的数量,降低内存占用,提高系统性能。适用于需要大量相似对象的场景。

注意事项:

  1. 线程安全考虑: 如果多个线程同时访问享元对象,需要确保共享状态的线程安全性,或者在享元对象内部进行适当的同步。
  2. 外部状态的管理: 外部状态由客户端管理,因此需要注意外部状态的一致性和正确传递。客户端在使用享元对象时需要提供正确的外部状态。
  3. 不可变性: 内部状态应该设计成不可变的,以确保在共享时不会出现意外的修改。
  4. 适用性限制: 享元模式适用于需要创建大量相似对象的场景。在一些情况下,如果对象差异较大,共享的效果可能并不显著,甚至可能导致代码复杂化。
  5. 合理使用: 仅在真正需要共享内部状态并且对象数量庞大时才使用享元模式,避免过度设计。在一些小规模的应用中,可能并不需要引入享元模式。

优点:

  1. 减少内存占用: 通过共享相似对象的内部状态,减少了对象的数量,从而减小了内存占用,提高了系统性能。
  2. 提高性能: 由于减少了对象的数量,操作相似对象的性能得到提升。在需要大量相似对象的场景下,可以有效减少系统开销。
  3. 外部状态独立: 外部状态由客户端负责传递,使得享元对象能够在不同的环境中被共享,而外部状态的变化不会影响到内部状态。
  4. 灵活性: 客户端可以通过传递不同的外部状态给享元对象,实现不同的行为,增加了灵活性和可扩展性。

缺点:

  1. 复杂性增加: 引入享元模式可能会增加系统的复杂性,特别是在需要维护共享池和管理外部状态的情况下。
  2. 不适用所有场景: 享元模式并不适用于所有的场景,只有在有大量相似对象需要共享时才能发挥其优势。在对象差异较大的情况下,可能并不能明显地减小内存占用。

应用场景:

  1. 文本编辑器: 在文本编辑器中,字符可以被看作是享元对象。相同的字符可以被多次使用,从而减小内存占用。
  2. 图形图像处理: 在图形图像处理中,像素颜色、字体等属性可以被设计为享元对象,以便有效地处理图像中大量相似的元素。
  3. 游戏开发: 在游戏中,大量相似的角色、道具等元素可以使用享元模式,减小内存占用,提高性能。
  4. 连接池: 数据库连接池、线程池等资源池的管理中,可以使用享元模式共享可复用的资源。
  5. 网络连接管理: 在网络连接管理中,可以使用享元模式共享相同的网络连接对象,以减小连接数和提高性能。

总体而言,享元模式适用于需要大量相似对象的场景,并能够在合适的情况下提高系统性能,减小内存占用。在实际应用中,需要根据具体的业务需求来判断是否采用享元模式。

4. 内部状态和外部状态

在软件设计中,对象的状态分为两种:内部状态(Intrinsic State)和外部状态(Extrinsic State)。这种状态的划分有助于更好地理解对象的行为和属性,尤其在使用享元模式时更为重要。

内部状态(Intrinsic State):

  • 定义: 内部状态是对象的本质属性,它决定了对象的基本行为和特征。这部分状态是对象内在的,独立于对象被创建或被应用的上下文。内部状态对于对象的行为和特性是稳定和不变的。
  • 特点: 内部状态是可以被共享的,多个对象可以共享相同的内部状态。这是享元模式中重要的共享部分。通常,内部状态应该设计成不可变的,以确保在共享时不会出现意外的修改。
  • 示例: 在文本编辑器中,字符的 Unicode 码可以被视为内部状态。对于相同的字符,其 Unicode 码是相同的,因此可以被多个字符对象共享。

外部状态(Extrinsic State):

  • 定义: 外部状态是对象的补充信息,它可能随着时间的推移而变化,而且可能是不可共享的。外部状态对于对象的行为和外观有影响,但不影响对象的基本特征。
  • 特点: 外部状态是不可共享的,每个对象都可能具有不同的外部状态。在使用享元模式时,外部状态需要在对象被使用时由客户端显式地传递给对象。
  • 示例: 在文本编辑器中,字符的颜色、字体大小等属性可以被视为外部状态。这些属性可能因为不同的上下文或用户需求而不同。

总结:

  • 内部状态和外部状态的关系: 内部状态和外部状态共同构成了对象的完整状态。内部状态是对象的本质特征,而外部状态是对象的可变和环境相关的属性。
  • 在享元模式中的应用: 享元模式通过区分内部状态和外部状态,实现了对对象内部状态的共享,从而优化了系统的性能和资源利用。
目录
相关文章
|
7天前
|
设计模式 Java
【设计模式系列笔记】责任链模式
责任链模式是一种行为设计模式,它允许你将请求沿着处理者链进行传递,直到有一个处理者能够处理它。每个处理者都有一个对下一个处理者的引用。责任链模式常用于处理请求的场景,例如在一个请求需要经过多个步骤或者多个对象来处理的情况下。
23 0
|
7天前
|
设计模式 缓存 监控
【设计模式系列笔记】代理模式
代理模式是一种结构型设计模式,它允许一个对象(代理对象)控制另一个对象的访问。代理对象通常充当客户端和实际对象之间的中介,用于对实际对象的访问进行控制、监控或其他目的。
43 1
|
7天前
|
设计模式 Java 开发者
【搞懂设计模式】享元模式:共享节约,皆大欢喜!
【搞懂设计模式】享元模式:共享节约,皆大欢喜!
12 0
|
7天前
|
设计模式
【设计模式】张一鸣笔记:责任链接模式怎么用?
【设计模式】张一鸣笔记:责任链接模式怎么用?
11 1
|
7天前
|
设计模式 算法 Java
【设计模式系列笔记】设计模式与设计原则
设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。 设计原则是一些通用的设计指导方针,它们提供了如何设计一个优秀的软件系统的基本思想和规则。指导着设计者如何组织代码以实现高内聚、低耦合、易扩展和易维护的软件系统。
26 4
|
7天前
|
设计模式 JavaScript 前端开发
vue的设计模式_笔记
vue的设计模式_笔记
16 0
|
7天前
|
设计模式 存储 Java
[设计模式Java实现附plantuml源码~结构型]实现对象的复用——享元模式
[设计模式Java实现附plantuml源码~结构型]实现对象的复用——享元模式
|
7天前
|
设计模式 Go
[设计模式 Go实现] 结构型~享元模式
[设计模式 Go实现] 结构型~享元模式
|
7天前
|
设计模式 算法 编译器
【设计模式系列笔记】访问者模式
访问者模式是一种行为设计模式,旨在将算法与对象结构分离,使得能够在不修改元素类的前提下定义新的操作。这一模式的核心思想是在元素类中添加一个接受访问者的方法,从而实现在不同元素上执行不同操作的能力。
33 0
|
7天前
|
设计模式 SQL 算法
【设计模式系列笔记】模板方法模式
模板方法模式是一种行为设计模式,它定义了一个算法的骨架,并允许子类在不改变该算法结构的情况下重新定义算法的某些步骤。这种模式属于行为型模式,它通过将算法的不同部分封装在不同的方法中,从而使子类能够在不改变算法结构的前提下定制算法的某些步骤。
33 0