【设计模式深度剖析】【7】【结构型】【享元模式】| 以高脚杯重复使用、GUI中的按钮为例说明,并对比Java类库设计加深理解

👈️上一篇:外观模式 | 下一篇:结构型设计模式对比👉️
设计模式-专栏👈️

目录

享元模式

享元模式Flyweight Pattern )是一种高效利用内存的设计模式,它像是一个"对象共享池",通过重用内存中已有的相似对象,避免了大量相同对象的重复创建,从而显著减少了内存占用,提升了系统性能。

在需要处理大量相似对象的场景下,享元模式就像一个"智能管家",通过精细管理对象,让系统更加"轻盈"和"高效"。

==>++本文源码,点击查看👈️++<==

定义

英文原话

  • Use sharing to support large numbers of fine-grained objects efficiently.

直译

  • 使用共享对象来有效地支持大量的细粒度对象。

如何理解?

享元模式(Flyweight Pattern)是池技术的一种重要实现方式。

享元模式的核心思想 是通过共享已经存在的对象实例来避免创建大量重复的、细粒度的对象,从而减少内存使用和提高性能。它通常适用于当对象数量非常大,且其中许多对象可以共享相同的内部状态的情况。通过将状态分为内部状态和外部状态,并仅存储和共享内部状态,可以显著降低内存占用。

字面理解

享元模式(Flyweight Pattern)的字面意思可以理解为"轻量级"或"共享的重量"。

在软件设计中,享元模式是一种结构型设计模式,用于减少应用程序中对象的数量,以减少内存占用和提高性能。它主要通过++共享对象实例(即"共享的重量")++ 来实现这一点,++而不是为每个请求都创建一个新的对象实例(即避免"重量级"的对象创建)++。

享元模式的核心思想是将对象的内部状态(intrinsic state)和外部状态(extrinsic state)进行分离。内部状态是存储在享元对象内部的信息,并且不会在享元对象之间改变。外部状态是依赖于上下文或操作状态的信息,它会在享元对象的不同使用场合之间变化。通过将外部状态从享元对象中分离出来,可以在多个请求之间共享相同的享元对象实例,从而减少了对象的数量。

这种优化特别适用于那些具有大量相似对象的应用场景,比如在一个图形用户界面(GUI)中,可能有大量的按钮或图标,它们的状态和行为可能是相同的,但位置不同。

例子:高脚杯的重复使用

想象一下,我是一家高档餐厅的经理。在这家餐厅里,为了给客人提供优雅的用餐体验,我决定使用高脚杯来盛放各种美酒。然而,由于餐厅客流量大,如果每次客人都使用全新的高脚杯,那么不仅成本高昂,而且也不环保。

为了解决这个问题,我决定引入一个"高脚杯复用系统"。在这个系统中,我预先准备了一批干净、高雅的高脚杯(享元对象),这些高脚杯都有相同的形状和设计(内部状态)。当客人需要品尝美酒时,我从复用系统中取出一个高脚杯,根据客人的需求装上相应的美酒(外部状态),然后送到客人的餐桌上。

当客人用餐结束后,我会将高脚杯收回,并安排专人进行清洗和消毒,确保它们干净无菌。之后,这些高脚杯会再次被放回到复用系统中,等待下一次使用。

通过引入这个"高脚杯复用系统",我不仅大大降低了餐厅的运营成本,减少了资源的浪费。同时,由于有一个集中的复用系统来管理高脚杯,我也能够更方便地追踪和控制高脚杯的数量和状态,确保为客人提供持续优质的服务。

在软件开发中,享元模式也是同样的道理。它帮助我们复用那些具有相同内部状态的对象,通过共享这些对象来减少内存占用和提高性能。同时,享元模式还提供了一个集中的工厂来管理和控制对象的创建和访问,使得代码更加易于管理和维护。

(代码示例见:示例解析:高脚杯重复使用案例)

例子:GUI中的按钮

在图形用户界面(GUI)中,享元模式的应用特别明显,特别是在处理大量具有相似状态和行为但位置不同的对象时。以下是一个具体的例子,说明享元模式在GUI按钮和图标中的应用:

在一个复杂的GUI应用程序中,如一个桌面应用或游戏界面,可能会有成百上千的按钮。这些按钮在功能、样式或行为上可能是相似的,但它们在界面上的位置不同。

传统方式

如果不使用享元模式,我们可能会为每个按钮创建一个新的按钮对象实例。这意味着每个按钮都会占用一定的内存空间,并且如果有大量的按钮,内存消耗会非常大。同时,由于每个按钮都需要单独管理,这也会增加代码复杂性和维护成本。

使用享元模式

在享元模式中,我们可以将按钮的"内部状态"和"外部状态"进行分离。内部状态是按钮共有的属性,如按钮的点击事件处理函数、按钮的样式(如颜色、字体等)、按钮的激活/禁用状态等。这些状态可以在多个按钮之间共享。

外部状态则是与按钮在界面上的位置相关的属性,如按钮的坐标、大小等。这些状态是依赖于特定上下文或操作状态的,因此不能共享。

通过享元模式,我们可以创建一个享元工厂来管理按钮对象。当需要创建一个新的按钮时,享元工厂会检查是否已经存在具有相同内部状态的按钮对象。如果存在,享元工厂就会返回该对象的引用,而不是创建一个新的对象。如果不存在,享元工厂就会创建一个新的按钮对象,并将其存储在工厂中以便复用。

(代码示例见:示例解析:GUI中的按钮)

4个角色

享元模式的角色及其具体内容如下:

1. Flyweight (抽象享元类)

定义享元对象的接口,声明了享元对象的公有方法。这些方法可以向享元对象中传入外部状态,并允许外部状态与享元对象的内部状态进行交互。这个接口使得享元对象可以被共享。

2. ConcreteFlyweight (具体享元类)

实现Flyweight接口,并为内部状态(如果有的话)提供存储空间。内部状态是存储在ConcreteFlyweight对象中的信息,这些信息可以在多个对象中共享。ConcreteFlyweight提供操作内部状态的具体实现,并可能需要存储额外的外部状态信息。

3. UnsharedFlyweight (非共享享元类)

可选角色)代表那些不需要被共享的Flyweight子类。它们通常包含那些不能或不应该被共享的状态信息。

4. FlyweightFactory (享元工厂类)

负责创建和管理享元对象。它跟踪已经创建的享元对象,并在客户端请求时提供现有的享元对象(如果可用)或创建新的享元对象。享元工厂通常使用某种存储结构(如哈希表)来存储和管理享元对象。

5. Client (客户端)

客户端是使用享元模式的代码部分。它通过享元工厂请求享元对象,并将外部状态传递给享元对象。客户端可以执行对享元对象的操作,并且不需要关心享元对象是如何被创建和管理的。

UML类图

代码示例

java 复制代码
package com.polaris.designpattern.list2.structural.pattern7.flyweight.classicdemo;

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

// 抽象享元类 (Flyweight)
interface Flyweight {  
    void operation(UnsharedContext context);  
}  
  
// 具体享元类 (ConcreteFlyweight)  
class ConcreteFlyweight implements Flyweight {  
    private String intrinsicState; // 内部状态  
  
    public ConcreteFlyweight(String state) {  
        this.intrinsicState = state;  
    }  
  
    @Override  
    public void operation(UnsharedContext context) {  
        // 访问内部状态和外部状态  
        System.out.println("Intrinsic: " + intrinsicState + ", Extrinsic: " + context.getExtrinsicState());  
    }  
}  
  
// 非共享享元类(如果需要的话)  
// 这里不直接实现Flyweight接口,而是作为一个包含外部状态的类  
class UnsharedContext {  
    private String extrinsicState;  
  
    public UnsharedContext(String state) {  
        this.extrinsicState = state;  
    }  
  
    public String getExtrinsicState() {  
        return extrinsicState;  
    }  
}  
  
// 享元工厂类 (FlyweightFactory)  
class FlyweightFactory {  
    private Map<String, Flyweight> flyweights = new HashMap<>();
  
    public Flyweight getFlyweight(String key) {  
        Flyweight flyweight = flyweights.get(key);  
        if (flyweight == null) {  
            flyweight = new ConcreteFlyweight(key);  
            flyweights.put(key, flyweight);  
        }  
        return flyweight;  
    }  
}  
  
// 客户端 (Client)  
public class FlyweightPatternDemo {  
    public static void main(String[] args) {  
        FlyweightFactory factory = new FlyweightFactory();  
  
        Flyweight flyweightA = factory.getFlyweight("A");  
        Flyweight flyweightB = factory.getFlyweight("B");  
  
        UnsharedContext contextA = new UnsharedContext("X");  
        UnsharedContext contextB = new UnsharedContext("Y");  
  
        flyweightA.operation(contextA); // 输出: Intrinsic: A, Extrinsic: X  
        flyweightB.operation(contextB); // 输出: Intrinsic: B, Extrinsic: Y  
  
        // 再次请求相同的Flyweight,应该是同一个实例  
        Flyweight flyweightA_again = factory.getFlyweight("A");  
        System.out.println(flyweightA == flyweightA_again); // 输出: true  
    }  
}
/* Output:
Intrinsic: A, Extrinsic: X
Intrinsic: B, Extrinsic: Y
true
*///~

在这个示例中,

Flyweight 是一个接口,定义了享元对象应该具有的方法。

ConcreteFlyweight 是实现了 Flyweight 接口的具体享元类,它存储了内部状态,并实现了 operation 方法来操作内部状态和外部状态。

UnsharedContext 类表示外部状态,它通常不由享元工厂管理,而是由客户端在需要时创建。

FlyweightFactory 是享元工厂类,负责创建和管理享元对象。

FlyweightPatternDemo 类是客户端,它使用享元工厂获取享元对象,并传递外部状态给享元对象进行操作。

享元模式的优缺点

优点:

  1. 减少内存占用:通过复用对象实例,享元模式能够显著减少系统中对象的数量,从而节省内存空间。
  2. 提高性能:由于减少了对象的创建和销毁,享元模式能够提升应用程序的性能,特别是在处理大量相似对象时。
  3. 更易于管理:享元工厂集中管理了享元对象,这使得对象的创建和访问更加统一和可控。

缺点:

  1. 增加系统复杂性:享元模式要求区分内部状态和外部状态,这可能会增加系统的复杂性。开发人员需要仔细设计以确保正确实现享元模式。
  2. 可能增加代码量:为了实现享元模式和区分状态,可能需要编写更多的代码来管理享元对象和外部状态。
  3. 不适合所有情况:享元模式主要适用于处理大量相似对象的情况。如果系统中对象数量较少,或者对象之间的差异很大,那么使用享元模式可能并不合适。

使用场景

享元模式通常用于以下场景:

  1. 大量相似对象:当系统中存在大量相似对象,且这些对象的内部状态可以共享时,可以使用享元模式来复用这些对象,以减少内存占用和提高性能。
  2. 频繁创建和销毁对象:如果系统中频繁创建和销毁大量相似对象,使用享元模式可以显著减少对象的创建和销毁次数,从而提高性能。
  3. 需要集中管理对象:当需要集中管理对象的创建和访问时,可以使用享元工厂来统一管理和控制享元对象的创建和访问。

Java类库说明

Integer类

在Java类库中,虽然没有一个直接对应享元模式的类,但有一些设计思想或实现与享元模式相似。不过,为了更直接地说明享元模式在Java类库中的应用,我们可以参考java.lang.Integer类的缓存机制,它使用了类似享元模式的思想来缓存一定范围内的整数对象。

Java的自动装箱(autoboxing)和拆箱(unboxing)特性使得我们可以在整数和Integer对象之间自动转换。为了提高性能和减少内存使用,Java为-128127之间的整数创建了一个对象缓存池。当我们在这个范围内进行装箱操作时(如int转换为Integer),Java会直接从缓存池中获取对象,而不是每次都创建一个新的对象。

以下是Integer类中与缓存相关的源码片段(简化版):

java 复制代码
public final class Integer extends Number implements Comparable<Integer> {  
    // ... 其他代码 ...  
  

    
    /** 
     * IntegerCache是一个内部类,用于配置缓存范围(但通常这个范围在JVM启动时就已经固定了)  
     * 缓存的Integer实例数组。  
     * 在这个范围内的值将被缓存。  
     */  
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
			//省略配置java.lang.Integer.IntegerCache.high参数改变最大值的设置的逻辑这里以默认的h=127为例
            high = h;
		
            cache = new Integer[(high - low) + 1];
            int j = low;
            //初始化缓存-128~127
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
    
    
   
    /** 
     * 判断是否在缓存区间内,在的话直接返回。使用内部类进行了封装
     *
     * 返回表示指定整数值的Integer实例。  
     * 如果该值在Integer缓存范围内,则直接从缓存中返回,否则返回一个新的Integer实例。  
     */  
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
   
    // ... 其他代码 ...  
   
}

在上面的代码中,我们可以看到Integer类的内部类有一个名为cache的静态数组,用于存储缓存的Integer对象。在静态初始化块中,-128127之间的整数被预先创建并存储在cache数组中。valueOf(int i)方法用于返回指定整数值的Integer实例。如果整数在缓存范围内,它直接从缓存中返回对应的对象;否则,它创建一个新的Integer对象。

这种设计思想与享元模式非常相似,因为它通过复用对象来减少内存使用和提高性能。然而,需要注意的是,Integer的缓存机制并不是完整的享元模式实现,因为它没有显式的享元工厂来管理和控制对象的创建。但是,它的设计思想和目的与享元模式是一致的。

String类

Java的String类库没有直接体现享元模式(Flyweight Pattern)的设计,但它采用了一些机制来优化字符串的使用,以减少内存消耗和提高性能。这些机制虽然与享元模式的思想有相似之处,但并不完全符合享元模式的定义。

以下是String类库中与性能优化和内存使用相关的几个关键点:

  1. 字符串不可变性(Immutability) :Java中的String对象是不可变的,这意味着一旦创建了一个字符串对象,它的内容就不能再被修改。这种不可变性带来了很多好处,其中之一就是可以安全地在多个地方共享相同的字符串实例,而不用担心它被意外修改。然而,这并不意味着String类本身使用了享元模式来复用字符串对象。
  2. 字符串常量池(String Constant Pool) :在Java中,使用字面值创建的字符串(例如"hello")会被自动放入一个称为"字符串常量池"的特殊内存区域中。如果后续代码中再次使用相同的字面值创建字符串,JVM会检查常量池中是否已经存在相同的字符串,如果存在则直接返回对该字符串的引用,而不是创建一个新的对象。这种机制可以减少不必要的字符串对象创建,从而节省内存和提高性能。然而,这种机制并不是由String类本身实现的,而是由JVM在运行时管理的。
  3. intern()方法String类提供了一个intern()方法,该方法可以将一个字符串添加到字符串常量池中(如果该字符串尚未存在于池中)。如果池中已经存在具有相同内容的字符串,则该方法返回对该字符串的引用;否则,它将在池中创建一个新的字符串对象,并返回对该对象的引用。这种方法提供了一种在运行时动态地将字符串添加到常量池中的机制,从而可以利用常量池的优化效果。但是,这并不意味着String类使用了享元模式来管理其对象。

虽然String类库中的这些机制与享元模式的思想有相似之处(即复用对象以减少内存使用和提高性能),但它们并不完全符合享元模式的定义。享元模式通常涉及一个显式的享元工厂类来管理和控制对象的创建和访问,而String类库并没有这样的工厂类。此外,享元模式通常用于处理大量具有相同或相似状态的对象,而String对象通常具有不同的内容(即不同的内部状态)。因此,虽然String类库中的优化机制与享元模式有相似之处,但它们并不完全相同。

示例解析:GUI中的按钮

在GUI应用程序中,我们可以通过以下方式应用享元模式:

  1. 定义享元接口:创建一个表示按钮的接口,定义按钮的基本操作(如点击事件处理函数、设置样式等)。
  2. 实现具体享元:实现享元接口,定义按钮的内部状态,并提供操作这些状态的方法。
  3. 创建享元工厂:创建一个享元工厂类,用于创建和管理按钮对象。享元工厂应该提供一个方法来获取按钮对象,该方法接受一些参数(如按钮的样式、点击事件处理函数等)来指定按钮的内部状态。如果工厂中已经存在具有相同内部状态的按钮对象,则返回该对象的引用;否则,创建一个新的按钮对象并存储在工厂中。
  4. 在GUI中使用享元:在GUI代码中,通过享元工厂获取按钮对象,并设置其外部状态(如坐标、大小等),然后将其添加到界面中。由于按钮的内部状态已经在享元工厂中进行了复用,因此可以显著减少内存消耗并提高性能。

通过这种方式,我们可以在不牺牲功能性和灵活性的前提下,有效地管理GUI中的大量按钮对象,减少内存消耗并提高应用程序的性能。

UML类图

上面的类图中,

ButtonFlyweight是抽象享元类,定义显示按钮的方法,需要传入外部状态,就是坐标(x, y)位置,即在传入的位置处显示按钮;

ButtonFlyweightFactory享元工厂类,它通过map维护按钮对象池,当然会组合多个享元类了,因此看到1:n的组合箭头;另外由于按钮对象池中的对象内部状态是事件监听器,即事件监听器是按钮对象的标识,每个事件监听器对应一个按钮对象,事件监听器对象就会有多个,因此看到1:n的组合箭头;

ConcreteButtonFlyweight具体享元,它的内部状态就是一个事件监听器,因此它有一个指向监听器的1:1的组合箭头;

客户端测试类创建的享元工厂类,享元工厂类会创建并维护享元对象池,因此可以看到相应的create箭头标识;

ActionListener事件监听器内部就一个方法,actionPerformed(ActionEvent e)事件处理函数,即点击按钮会发生什么,因此可以用lambda表达式实现。

代码示例

下面是一个简单的Java代码示例,展示了如何使用享元模式来管理GUI中的按钮对象。

在这个例子中,我们展示了如何使用享元模式来管理GUI中的按钮对象,其中具有相同点击事件处理函数的按钮可以共享同一个享元实例,以节省内存。如果按钮的点击事件处理函数不同,那么它们将拥有不同的享元实例。

java 复制代码
package com.polaris.designpattern.list2.structural.pattern7.flyweight.guidemo;

import java.awt.Point;
import java.awt.event.ActionEvent;  
import java.awt.event.ActionListener;  
import java.util.HashMap;  
import java.util.Map;  
  
// 享元接口:定义按钮的公共操作  
interface ButtonFlyweight {  
    void display(Point position); // 显示按钮的方法,需要外部状态(位置)  
}  
  
// 具体享元:实现按钮的公共操作,存储内部状态  
class ConcreteButtonFlyweight implements ButtonFlyweight, ActionListener {  
    // 假设的内部状态:点击事件处理函数  
    private final ActionListener clickListener;  
  
    public ConcreteButtonFlyweight(ActionListener clickListener) {  
        this.clickListener = clickListener;  
    }  
  
    @Override  
    public void actionPerformed(ActionEvent e) {  
        // 处理点击事件,这里只是简单地打印消息  
        System.out.println("Button clicked at " + e.getSource());  
    }  
  
    @Override  
    public void display(Point position) {  
        // 这里只是模拟显示按钮,实际上会在GUI框架中绘制按钮  
        System.out.println("Displaying button at position: " + position);  
        // 在真实的GUI应用中,我们需要触发点击事件处理,但这里只是模拟  
        // clickListener.actionPerformed(new ActionEvent(this, 0, "Click"));  
    }  
}  
  
// 享元工厂:负责创建和管理享元对象  
class ButtonFlyweightFactory {  
    private Map<ActionListener, ButtonFlyweight> flyweights = new HashMap<>();  
  
    public ButtonFlyweight getFlyweight(ActionListener clickListener) {  
        ButtonFlyweight flyweight = flyweights.get(clickListener);  
        if (flyweight == null) {  
            flyweight = new ConcreteButtonFlyweight(clickListener);  
            flyweights.put(clickListener, flyweight);  
        }  
        return flyweight;  
    }  
}  
  
// 客户端代码  
public class ButtonFlyweightClient {  
    public static void main(String[] args) {  
        // 创建一个享元工厂  
        ButtonFlyweightFactory factory = new ButtonFlyweightFactory();  
  
        // 定义一个共享的点击事件处理函数  
        ActionListener clickListener = e -> {  
            // 处理点击事件的逻辑  
            System.out.println("Button clicked!");  
        };  
  
        // 获取享元对象,并设置外部状态(位置)来"显示"按钮  
        ButtonFlyweight button1 = factory.getFlyweight(clickListener);  
        button1.display(new Point(10, 10)); // 在位置(10, 10)显示按钮  
  
        // 再次获取相同的享元对象,并设置不同的外部状态来"显示"另一个按钮  
        // 注意这里不会创建新的对象实例,而是复用了之前的对象  
        ButtonFlyweight button2 = factory.getFlyweight(clickListener);  
        button2.display(new Point(50, 50)); // 在位置(50, 50)显示另一个按钮  
  
        // 如果有不同的点击事件处理函数,将会创建新的享元对象  
        // ...  
    }  
}

/* Output:
Displaying button at position: java.awt.Point[x=10,y=10]
Displaying button at position: java.awt.Point[x=50,y=50]
*///~

在这个例子中,我们定义了一个ButtonFlyweight接口来表示按钮的公共操作,包括一个display方法来模拟在GUI中显示按钮(需要外部状态:位置)。ConcreteButtonFlyweight类实现了这个接口,并存储了按钮的内部状态(点击事件处理函数)。ButtonFlyweightFactory类作为享元工厂,负责管理和复用按钮对象。客户端代码通过工厂获取按钮对象,并设置其外部状态来模拟在GUI中显示按钮。由于两个按钮具有相同的内部状态(点击事件处理函数),因此它们共享了同一个ConcreteButtonFlyweight实例。

返回

示例解析:高脚杯重复使用案例

为了方便说明享元模式,假设家中有不同质地装饰的高脚杯仅仅各一只,比如金质装饰高脚杯一只,银质装饰高脚杯一只。

喝不同饮料使用不同的高脚杯,等喝完饮料,高脚杯洗刷干净,放置起来,下次喝饮料再拿出来,继续使用。

而不是说每次喝饮料就要买一只新的高脚杯,高脚杯是可以重复使用的。

UML类图

从类图上看到享元工厂类组合享元类,且是1 : n的关系,也就是它内部的共享对象池,由于当所需的对象不存在时,他会创建享元对象,因此会有《create》标识;工厂对外提供获取酒杯对象的方法,通过传入内部状态即decorationType区分不同的高脚杯(这里做了个前提假设:家中有不同质地装饰的高脚杯仅仅各一只,以解决map的key只获取到一个对象实例)

具体享元类实现了抽象享元类的useGoblet(String)方法,同时有一个私有属性,decorationType即内部状态,也是区分不通享元对象的标识。在这里就是金质装饰还是银质装饰还是其他什么类型的装饰的高脚杯;而useGoblet(String)方法传参就是用来盛放什么饮料,是酒水还是白开水,还是什么喝的之类的吧。

代码示例

java 复制代码
package com.polaris.designpattern.list2.structural.pattern7.flyweight.gobletdemo;

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

// 享元接口  
interface GobletFlyweight {
    void useGoblet(String purpose);
}

// 具体享元实现  
class ConcreteGobletFlyweight implements GobletFlyweight {
    private String decorationType;

    public ConcreteGobletFlyweight(String decorationType) {
        this.decorationType = decorationType;
    }

    @Override
    public void useGoblet(String purpose) {
        System.out.println("Using goblet with " + decorationType + " for: " + purpose);
    }
}

// 享元工厂  
class GobletFlyweightFactory {
    // 使用Map来存储已经创建的高脚杯享元对象  
    private Map<String, GobletFlyweight> gobletFlyweights = new HashMap<>();

    // 获取高脚杯享元对象  
    public GobletFlyweight getGoblet(String decorationType) {
        GobletFlyweight goblet = gobletFlyweights.get(decorationType);
        if (goblet == null) {
            // 根据装饰类型创建新的高脚杯享元对象  
            goblet = new ConcreteGobletFlyweight(decorationType);
            gobletFlyweights.put(decorationType, goblet); // 将新创建的高脚杯享元对象存储在Map中  
        }
        return goblet;
    }
}

// 客户端代码  
public class GobletFlyweightClient {
    public static void main(String[] args) {
        // 创建一个享元工厂  
        GobletFlyweightFactory factory = new GobletFlyweightFactory();

        // 通过工厂获取高脚杯享元对象,每个享元对象具有不同的装饰  
        GobletFlyweight goblet1 = factory.getGoblet("gold");
        GobletFlyweight goblet2 = factory.getGoblet("silver");

        // 使用高脚杯  
        goblet1.useGoblet("drinking wine");
        goblet2.useGoblet("serving water");

        // 尝试获取已存在的高脚杯享元对象  
        GobletFlyweight goblet3 = factory.getGoblet("gold");

        // 验证goblet1和goblet3是否指向同一个对象  
        System.out.println(goblet1 == goblet3); // 应该输出 true  

        // 验证goblet1和goblet2是否指向不同的对象  
        System.out.println(goblet1 == goblet2); // 应该输出 false  
    }
}

/* Output:
Using goblet with gold for: drinking wine
Using goblet with silver for: serving water
true
false
*///~

在这个示例中,我们创建了一个GobletFlyweight接口和一个实现类ConcreteGobletFlyweight,它们表示高脚杯的享元对象。我们还创建了一个GobletFlyweightFactory工厂类,用于创建和存储高脚杯享元对象

GobletFlyweightFactory根据传入的装饰类型(decorationType)来返回不同的高脚杯享元对象,比如这里的金质,银质装饰

GobletFlyweightFactory中的getGoblet方法现在使用这个decorationType作为键来从Map中检索或创建新的享元对象。这样,我们就可以根据装饰类型来区分不同的高脚杯享元对象了。(这里做了个前提假设:家中有不同质地装饰的高脚杯仅仅各一只,以解决map的key只获取到一个对象实例)

在客户端代码中,我们通过工厂获取了高脚杯享元对象的引用,并使用了它们。最后,我们验证了这引用是否指向了同一个对象。

在实际应用中,享元模式通常用于处理大量相似但不完全相同的对象,以减少内存使用并提高性能。

返回


👈️上一篇:外观模式 | 下一篇:结构型设计模式对比👉️
设计模式-专栏👈️

相关推荐
shinelord明7 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
新手小袁_J12 分钟前
JDK11下载安装和配置超详细过程
java·spring cloud·jdk·maven·mybatis·jdk11
呆呆小雅13 分钟前
C#关键字volatile
java·redis·c#
Monly2113 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
Ttang2315 分钟前
Tomcat原理(6)——tomcat完整实现
java·tomcat
钱多多_qdd26 分钟前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring
waicsdn_haha28 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
Q_192849990638 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
Code_流苏41 分钟前
VSCode搭建Java开发环境 2024保姆级安装教程(Java环境搭建+VSCode安装+运行测试+背景图设置)
java·ide·vscode·搭建·java开发环境
禁默1 小时前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程