软件设计模式系列之十三——享元模式

软件设计,模式,系列,十三 · 浏览次数 : 157

小编点评

**享元模式** 享元模式是一种在减少内存占用和提高系统性能时使用的结构型设计模式。它通过共享对象和外部状态的内部状态,有效地降低系统的资源消耗。 **优点** *减少内存占用 *提高系统性能和效率 *分离内部状态和外部状态 *提高性能 **缺点** *增加复杂性 *可能导致线程安全问题 **应用** *创建型模式(如工厂模式、单例模式) *结构型模式(如装饰者模式、代理模式) *图形和图像处理应用 **其他** *享元模式通常与创建型模式、结构型模式和代理模式一起使用。 *它用于在需要创建大量相似对象时提高系统的效率和性能。

正文

1 模式的定义

享元模式(Flyweight Pattern)是一种结构型设计模式,它旨在减少内存占用或计算开销,通过共享大量细粒度对象来提高系统的性能。这种模式适用于存在大量相似对象实例,但它们的状态可以外部化(extrinsic),并且可以在多个对象之间共享的情况。

2 举例说明

为了更好地理解享元模式,让我们举一些现实生活中的例子。

咖啡店的咖啡杯和碟子的例子。在咖啡店中,咖啡杯和碟子通常具有相同的设计和形状,但它们可能具有不同的颜色或图案。咖啡店可以使用享元模式来共享相同设计的杯子和碟子,以减少存储和管理的成本。

公共交通卡的例子。城市中的公共交通卡(如地铁卡、公共汽车卡)通常具有相同的功能和外观,但每张卡可能包含不同的余额和个人信息。这些卡可以被视为享元对象,公共交通系统可以共享卡的通用功能。

电子书阅读器的字体和样式的例子。电子书阅读器可以使用享元模式来管理字体、字号和样式。多本电子书可以共享相同的字体和样式设置,以提供一致的阅读体验。

这些例子都涉及到具有相似属性和功能的对象,它们可以通过享元模式来共享通用部分,从而减少资源消耗并提高效率。这在设计和生产中可以节省时间和成本。

3 结构

享元模式的结构包括以下主要组件:

享元工厂(Flyweight Factory):享元工厂负责创建和管理享元对象。它维护一个享元池,其中包含已经创建的享元对象,并根据客户端请求共享已经存在的对象或创建新的享元对象。

享元接口(Flyweight Interface):享元接口是享元对象的抽象,通常声明了享元对象的公共方法,以便客户端能够访问和操作享元对象。

具体享元(Concrete Flyweight):具体享元是享元接口的实现,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态是不可共享的,它在运行时传递给享元对象。

客户端(Client):客户端是使用享元模式的应用程序或模块,它通过享元工厂来获取或共享享元对象,并根据需要传递外部状态。

4 实现步骤

要实现享元模式,可以按照以下步骤进行操作:

确定内部状态和外部状态:首先,确定对象的内部状态和外部状态。内部状态是可以被多个对象共享的部分,而外部状态是不可共享的。

创建享元接口:定义享元接口,声明享元对象的公共方法,包括操作内部状态和外部状态的方法。

创建具体享元类:实现具体享元类,它包含了内部状态和外部状态的具体实现。内部状态可以在多个对象之间共享,而外部状态需要在运行时传递。

创建享元工厂:创建享元工厂,负责创建和管理享元对象。享元工厂可以维护一个享元池,用于存储已经创建的享元对象。

客户端使用享元对象:在客户端中,通过享元工厂来获取或共享享元对象。客户端需要提供外部状态作为参数,并根据需要操作享元对象。

5 代码实现

以下是一个简单的 Java 代码示例,演示了如何使用享元模式来实现公共交通卡的共享功能。在这个示例中,我们创建了一个 TransportCardFactory 工厂类来管理交通卡对象,以及一个 TransportCard 接口表示交通卡。

// 1. 定义交通卡接口
interface TransportCard {
    void swipe();
}

// 2. 创建具体的交通卡类
class SubwayCard implements TransportCard {
    private String ownerName;
    private int balance;

    public SubwayCard(String ownerName) {
        this.ownerName = ownerName;
        this.balance = 0;
    }

    public void swipe() {
        System.out.println("刷地铁卡,扣除票价,余额:" + balance);
    }
}

// 3. 创建享元工厂类
class TransportCardFactory {
    private Map<String, TransportCard> cards = new HashMap<>();

    public TransportCard getCard(String ownerName) {
        if (cards.containsKey(ownerName)) {
            System.out.println("使用现有的交通卡:" + ownerName);
            return cards.get(ownerName);
        } else {
            System.out.println("创建新的交通卡:" + ownerName);
            TransportCard card = new SubwayCard(ownerName);
            cards.put(ownerName, card);
            return card;
        }
    }
}

// 4. 客户端代码
public class Client {
    public static void main(String[] args) {
        TransportCardFactory cardFactory = new TransportCardFactory();

        // 乘客1刷卡
        TransportCard card1 = cardFactory.getCard("zhanngsan");
        card1.swipe();

        // 乘客2刷卡
        TransportCard card2 = cardFactory.getCard("lisi");
        card2.swipe();

        // 再次刷卡
        TransportCard card3 = cardFactory.getCard("zhanngsan");
        card3.swipe();
    }
}

在这个示例中,我们首先定义了 TransportCard 接口,表示交通卡的通用功能。然后,我们创建了一个具体的交通卡类 SubwayCard,它实现了 TransportCard 接口,并包含了特定于地铁卡的属性。

接下来,我们创建了享元工厂类 TransportCardFactory,它负责管理和共享交通卡对象。当客户端需要一个交通卡时,工厂类会首先检查是否已经存在具有相同拥有者姓名的卡,如果存在则返回现有的卡,否则创建一个新的卡对象。

最后,我们在客户端代码中演示了如何使用享元模式,创建并刷卡,观察到当两位乘客使用相同姓名刷卡时,会共享同一个交通卡对象,从而减少了卡对象的创建和内存占用。

6 典型应用场景

6.1 享元模式通常在以下情况下得到广泛应用

  • 大量对象。当系统中存在大量相似对象实例时,使用享元模式可以显著减少内存占用,因为相似对象的内部状态可以共享。

  • 内部状态与外部状态。当对象可以分为内部状态和外部状态时,享元模式特别有用。内部状态是对象的固定部分,可以被多个对象共享,而外部状态是对象的可变部分,每个对象可以根据需要个性化。

  • 性能优化。在需要高性能和低内存消耗的情况下,享元模式可以用于共享重复使用的对象,从而提高系统的性能。

  • 缓存管理。在需要缓存大量对象以提高系统响应时间的情况下,可以使用享元模式来管理缓存对象。

  • 资源池管理。当需要管理共享资源池(如数据库连接池、线程池)中的资源对象时,享元模式可以用于有效地共享和重用资源。

享元模式在需要管理大量相似对象、共享内部状态、提高性能和减少内存占用的情况下非常有用。它允许对象在不同上下文中共享内部状态,而外部状态可以根据需要进行个性化定制。通过合理使用享元模式,可以改善系统的效率和资源利用率。

6.2 java中的字符串应用享元模式场景

在Java中,字符串是使用享元模式的经典示例。享元模式的核心思想是共享相似对象的内部状态,以减少内存占用。字符串的使用正是基于这个思想。

下面是Java中字符串如何使用享元模式的一些关键特点:

不可变性:Java中的字符串是不可变的,也就是说一旦创建了一个字符串对象,它的值就不能被修改。这意味着如果两个字符串具有相同的字符序列,它们可以共享相同的内部字符数组。

字符串常量池:Java维护了一个字符串常量池(String Pool),用于存储字符串字面量。当你创建一个字符串字面量时,Java会首先检查常量池中是否已经存在相同值的字符串。如果存在,它将返回常量池中的字符串引用,而不会创建新的对象。

共享相同的字符串对象:由于字符串的不可变性和字符串常量池的存在,多个字符串变量可以共享相同的字符串对象。这意味着如果你有多个字符串变量引用相同的字符串值,它们实际上共享同一个字符串对象。

下面是一个示例,演示了字符串如何使用享元模式:

String s1 = "Hello"; // 创建一个字符串字面量,存储在常量池中
String s2 = "Hello"; // 与s1共享相同的字符串对象

String s3 = new String("Hello"); // 创建一个新的字符串对象,不存储在常量池中
String s4 = new String("Hello"); // 创建另一个新的字符串对象,也不存储在常量池中

System.out.println(s1 == s2); // true,s1和s2共享相同的字符串对象
System.out.println(s1 == s3); // false,s1和s3引用不同的字符串对象

在上面的示例中,s1 和 s2 共享相同的字符串对象,因为它们引用相同的字符串字面量,而 s3 和 s4 创建了新的字符串对象,因为它们使用了 new 操作符。这种共享内部状态的方式减少了内存占用,并提高了性能,特别是当处理大量字符串时。

Java中的字符串是一个典型的享元模式的例子,通过不可变性和字符串常量池,它实现了字符串对象的共享,以减少内存占用和提高性能。这种设计对于处理字符串操作非常高效,并且保证了字符串值的安全性,因为它们不可被修改。

7 优缺点

享元模式具有一些优点和缺点,让我们来看看:

优点:

减少内存占用,享元模式通过共享相似对象的内部状态,可以大大减少内存占用,提高系统的性能和效率。提高性能,通过共享对象,减少了对象的创建和销毁,从而提高了系统的性能。分离内部状态和外部状态,享元模式允许将内部状态和外部状态分开,外部状态可以在运行时传递给享元对象,使系统更灵活。

缺点:

增加复杂性,享元模式引入了共享对象和外部状态的概念,可能增加了系统的复杂性。可能导致线程安全问题,如果多个线程同时访问共享对象并修改其外部状态,可能会导致线程安全问题。

8 类似模式

享元模式通常与其他设计模式一起使用,以解决更复杂的问题或实现更全面的系统。以下是一些常见的设计模式,以及如何与享元模式一起使用它们。

工厂模式。享元模式通常需要一个工厂来创建和管理共享对象。你可以使用工厂模式来创建享元对象,确保对象的创建和初始化过程是封装的,并且客户端不需要直接创建对象。

单例模式。在享元模式中,享元工厂可以是一个单例,以确保只有一个享元工厂实例用于管理共享对象。这样可以确保对象的唯一性和一致性。

装饰者模式:装饰者模式可以与享元模式一起使用,以动态地添加功能或状态到享元对象。装饰者模式允许你在不改变对象结构的情况下,为对象添加额外的行为。

代理模式:代理模式可以与享元模式一起使用,以提供对享元对象的访问控制或延迟加载。代理可以用于监控或限制对共享对象的访问。

组合模式:组合模式用于将对象组织成树形结构,享元模式可以用于共享组合中的相似对象,以减少内存占用。这在图形和图像处理应用中特别有用。

享元模式可以与许多其他设计模式一起使用,具体取决于系统的需求。它通常与创建型模式(如工厂模式、单例模式)和结构型模式(如装饰者模式、代理模式)结合使用,以实现更灵活、高效和可维护的系统。在实际应用中,将多种模式结合使用可以更好地满足复杂系统的需求。

9 小结

享元模式是一种有助于减少内存占用和提高系统性能的结构型设计模式。通过共享大量细粒度的对象,它可以有效地降低系统的资源消耗,特别适用于存在大量相似对象的场景。在设计和开发中,当需要创建大量相似对象时,可以考虑使用享元模式以提高系统的效率和性能。这种模式的核心思想是将对象的内部状态与外部状态分离,从而实现对象的共享和复用。

与软件设计模式系列之十三——享元模式相似的内容:

软件设计模式系列之十三——享元模式

享元模式(Flyweight Pattern)是一种结构型设计模式,它旨在减少内存占用或计算开销,通过共享大量细粒度对象来提高系统的性能。这种模式适用于存在大量相似对象实例,但它们的状态可以外部化(extrinsic),并且可以在多个对象之间共享的情况。

软件设计模式系列之二十三——策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它允许在运行时动态选择算法的行为。这意味着你可以定义一系列算法,将它们封装成独立的策略对象,然后根据需要在不修改客户端代码的情况下切换这些算法。策略模式有助于解决问题领域中不同行为的变化和扩展,同时保持代码的灵活性和可维护性。

软件设计模式系列之十一——装饰模式

装饰模式属于结构型设计模式,它通过将对象包装在装饰器类中来动态地添加额外的行为,而不需要修改原始对象的代码。这个模式以透明的方式向对象添加功能,从而使您可以根据需要组合各种功能。

软件设计模式系列之十二——外观模式

外观模式是一种结构型设计模式,它提供了一个简化的接口,用于访问系统中的一组相关接口,以隐藏系统的复杂性。外观模式的主要目标是简化客户端与子系统之间的交互,同时降低了系统的耦合度。它允许客户端通过一个统一的入口点来与系统进行通信,而不需要了解系统内部的具体细节和复杂性

软件设计模式系列之十四——代理模式

代理模式是一种结构型设计模式,它允许一个对象(代理)充当另一个对象的接口,以控制对该对象的访问。代理模式通常用于控制对真实对象的访问,以实现一些额外的功能,例如延迟加载、权限控制、日志记录等。这种模式属于结构型设计模式,因为它关注对象之间的组合,以形成更大的结构。

软件设计模式系列之十九——中介者模式

@目录1 模式的定义2 举例说明3 结构4 实现步骤5 代码实现6 典型应用场景7 优缺点8 类似模式9 小结 1 模式的定义 中介者模式是一种行为型设计模式,它用于降低对象之间的直接通信,通过引入一个中介者对象来管理对象之间的交互。这种模式有助于减少对象之间的耦合性,使系统更加可维护和扩展。中介者

软件设计模式系列之十八——迭代器模式

迭代器模式是一种行为型设计模式,它允许客户端逐个访问一个聚合对象中的元素,而不暴露该对象的内部表示。迭代器模式提供了一种统一的方式来遍历不同类型的集合,使客户端代码更加简洁和可复用。

软件设计模式系列之十七——解释器模式

解释器模式是一种行为型设计模式,它用于将一种语言或表达式解释为对象。该模式通过定义语言的文法规则,并使用解释器来解释和执行这些规则,将复杂的语言转换为对象的操作。 在软件开发中,解释器模式常用于处理类似于编程语言、查询语言、正则表达式等需要解释和执行的场景。它将一个复杂的语言分解为一系列简单的规则,...

软件设计模式系列之十六——命令模式

命令模式(Command Pattern)是一种行为型设计模式,旨在将请求发送者和接收者解耦,将一个请求封装为一个对象,从而允许您参数化客户端对象以进行不同的请求、排队请求或记录请求,并支持可撤销操作。 命令模式的核心思想是将一个请求包装成一个对象,包括请求的参数和接收者对象,然后客户端只需要调用该...

软件设计模式系列之十五——职责链模式

职责链模式(Chain of Responsibility Pattern)也称为责任链模式,是一种结构型设计模式,用于构建一条对象处理请求的责任链。在这个模式中,多个对象依次处理请求,直到其中一个对象能够处理该请求为止。职责链模式将请求的发送者和接收者解耦,允许多个对象都有机会处理请求,同时可以动...