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

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

设计模式-专栏👈️

目录

  • 享元模式
  • 定义
    • 英文原话
    • 直译
    • 如何理解?
      • 字面理解
      • 例子:高脚杯的重复使用
      • 例子:GUI中的按钮
        • 传统方式
        • 使用享元模式
  • 4个角色
    • 1. Flyweight (抽象享元类)
    • 2. ConcreteFlyweight (具体享元类)
    • 3. UnsharedFlyweight (非共享享元类)
    • 4. FlyweightFactory (享元工厂类)
    • 5. Client (客户端)
    • UML类图
    • 代码示例
  • 享元模式的优缺点
    • 优点:
    • 缺点:
  • 使用场景
  • Java类库说明
    • Integer类
    • String类
  • 示例解析:GUI中的按钮
    • UML类图
    • 代码示例
  • 示例解析:高脚杯重复使用案例
    • UML类图
    • 代码示例

享元模式

享元模式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类图

在这里插入图片描述

代码示例

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类中与缓存相关的源码片段(简化版):

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中的按钮对象,其中具有相同点击事件处理函数的按钮可以共享同一个享元实例,以节省内存。如果按钮的点击事件处理函数不同,那么它们将拥有不同的享元实例。

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)方法传参就是用来盛放什么饮料,是酒水还是白开水,还是什么喝的之类的吧。

代码示例

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只获取到一个对象实例)

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

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

返回


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

设计模式-专栏👈️

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/668157.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

html期末复习速览

一.基础标签 1.段落标签<p></p> 特点&#xff1a;分段分割 2.标题标签<h1></h1>……<h6></h6> 特点&#xff1a;文字加粗&#xff0c;单独占一行 3.换行标签<br /> 特点&#xff1a;单标签&#xff0c;强制换行 二.文本格式化…

流媒体内网穿透/组网/视频协议转换EasyNTS上云网关如何更改密码?

EasyNTS上云网关的主要作用是解决异地视频共享/组网/上云的需求&#xff0c;网页对域名进行添加映射时&#xff0c;添加成功后会生成一个外网访问地址&#xff0c;在浏览器中输入外网访问地址&#xff0c;即可查看内网应用。无需开放端口&#xff0c;EasyNTS上云网关平台会向Ea…

备忘录--

备忘录 vue新建项目&#xff0c;body 大小占不了全屏 // 解决问题 // public/index.html 更改基础样式 html,body{height: 100%width: 100%&#xff1b;margin: 0;border: 0;padding: 0; }Element Plus 经典布局 参考 Element Plus 官网 <template><div class"…

jmeter性能测试用例提取

性能测试用例提取参考(根据单、混合场景、模块、运行时间、优先级)去考虑 Tips&#xff1a;用例提取和用例设计不一样 后期可以结合性能测试用例设计(设置线程数、时间、循环、同步定时器等等&#xff09;来做性能测试

从零到一建设数据中台 - 关键技术汇总

一、数据中台关键技术汇总 语言框架&#xff1a;Java、Maven、Spring Boot 数据分布式采集&#xff1a;Flume、Sqoop、kettle 数据分布式存储&#xff1a;Hadoop HDFS 离线批处理计算&#xff1a;MapReduce、Spark、Flink 实时流式计算&#xff1a;Storm/Spark Streaming、…

VB.net进行CAD二次开发(四)

netload不能弹出对话框&#xff0c;参考文献2 参考文献1说明了自定义菜单的问题&#xff0c;用的是cad的系统命令 只要加载了dll&#xff0c;自定义的命令与cad的命令同等地位。 这时&#xff0c;可以将自定义菜单的系统命令替换为自定义命令。 <CommandMethod("Add…

开源代码分享(32)-基于改进多目标灰狼算法的冷热电联供型微电网运行优化

参考文献&#xff1a; [1]戚艳,尚学军,聂靖宇,等.基于改进多目标灰狼算法的冷热电联供型微电网运行优化[J].电测与仪表,2022,59(06):12-1952.DOI:10.19753/j.issn1001-1390.2022.06.002. 1.问题背景 针对冷热电联供型微电网运行调度的优化问题&#xff0c;为实现节能减排的目…

换电脑怎么软件搬家?最好的8个电脑迁移软件

您知道何时需要数据迁移吗&#xff1f;比方说。您可能需要数据迁移以确保安全、备份或将操作系统升级到最新版本。您对迁移软件有任何了解吗&#xff1f;如您所想&#xff0c;我们将在本文中提供有关数据迁移软件的信息。让我们继续阅读。 最佳数据迁移软件列表 数据的完整性在…

深入探讨npm、Yarn、pnpm和npx之间的区别

前端生态系统是一个快速发展的领域&#xff0c;充满了各种工具和技术。对于开发人员来说&#xff0c;跟上这些创新可能是一项艰巨的挑战。 在本文中&#xff0c;我们将深入探讨npm、Yarn、pnpm和npx之间的区别&#xff0c;帮助你理解每个工具的不同之处。 包管理器比较 npm …

视频监控汇聚EasyCVR助力山体滑坡/自然灾害可视化监测与应急救援

近日&#xff0c;巴布亚新几内亚恩加省遭遇山体滑坡&#xff0c;持续引发关注。截至5月29日&#xff0c;恩加省山体滑坡导致的遇难人数可能超过2000人。据悉&#xff0c;巴布亚新几内亚恩加省位于巴布亚新几内亚中部高原的西部&#xff0c;地形多山。由于事发地区相对偏远&…

Excel-多级联动下拉选择

Excel-多级联动下拉选择 1、定义名称 &#xff08;1&#xff09;省-市 &#xff08;2&#xff09;市-区 同样的操作将区也加入 2、设置下拉单元格 &#xff08;1&#xff09;省 &#xff08;2&#xff09;市 INDIRECT($F$2)&#xff08;3&#xff09;区 INDIRECT($G$2)

RabbitMQ(四)事务消息,惰性队列,优先队列

文章目录 事务消息概念配置 惰性队列概念应用场景 优先队列概念配置 事务消息 仅在生产者端有效&#xff0c;消费端无效 概念 总结&#xff1a; 在生产者端使用事务消息和消费端没有关系在生产者端使用事务消息仅仅是控制事务内的消息是否发送提交事务就把事务内所有消息都发送…

echarts学习:基本使用和组件封装

前言 我在工作中使用echarts较少&#xff0c;这就导致每次使用时都要从头再来&#xff0c;这让我很头疼。因此我决心编写一系列文章将我参与工作后几次使用echarts所用到的知识记录下来&#xff0c;以便将来可以快速查阅。 一、基本使用 像我一样的新手&#xff0c;想要入门e…

【设计模式】JAVA Design Patterns——Iterator(迭代器模式)

&#x1f50d;目的 提供一种在不暴露其基础表示的情况下顺序访问聚合对象的元素的方法。 &#x1f50d;解释 真实世界例子 百宝箱包含一组魔法物品。有多种物品&#xff0c;例如戒指&#xff0c;药水和武器。可以使用藏宝箱提供的迭代器按类型浏览商品。 通俗描述 容器可以提供…

d3dcompiler43.dll丢失怎么修复,分享几种有效的修复教程

电脑已经成为我们生活中不可或缺的一部分。然而&#xff0c;由于各种原因&#xff0c;电脑可能会出现一些问题&#xff0c;其中之一就是d3dcompiler43.dll文件丢失。这个文件是DirectX组件之一&#xff0c;用于编译和链接DirectX应用程序。当这个文件丢失时&#xff0c;可能会导…

Chapter 5 Current Mirrors and Biasing Techniques

Chapter 5 Current Mirrors and Biasing Techniques 这一章介绍电流镜 5.1 Basic Current Mirrors MOS工作在饱和区, 表现为一个电流源. 考虑电源变化, PVT波动, 我们会首先产生一路精准电流源, 再复制这路电流. 如何复制呢? 电流转电压再转电流就行了, 如下图所示 考虑到…

SpringBoot+VUE(选课)课堂管理系统

免费获取方式↓↓↓ 项目介绍035&#xff1a; 项目名:课堂管理系统 技术栈:SpringBootVue 网址:http://localhost:8081/ 学生 1 密码 1 教师 20190101 密码 zsf 功能: 首页 课程服务 学生服务 选课 我的选课 教师服务 我的授课 笔记 签到 聊天室 二、技术栈 所有场景都支持 适…

wampserver的使用

wampserver的使用 文章目录 wampserver的使用1.启动2.目录3.基本操作 1.启动 WampServler有三种状态 服务器关闭状态&#xff0c;颜色为红色服务器开启&#xff0c;但是为离线状态&#xff0c;颜色为橙色&#xff0c;只有本机可以访问服务器开启&#xff0c;在线状态&#xf…

万字长文,小白新手怎么开始做YOLO实验,从零开始教!整体思路在这里,科研指南针!

最近专栏来了很多的新手小白&#xff0c;对科研实验的过程感到困惑和无从下手&#xff0c;这篇文章就来讲解一下整体的科研流程&#xff0c;从选择数据集到发表论文的各个步骤&#xff0c;并针对大家在实验中常犯的错误进行解答。并且为大家提供通向我其他相关博客的指引&#…

远程服务器上,再次配笔记1、2、11、12相同的深度学习和gcc环境

文章目录 参考文献 创建环境在(zgp_m3dm)中安装会显示zgp_m3dm_main环境中的情况满足(base) ~/zgp/M3DM-repetition conda deactivate 再次安装如果服务器本身不能联网&#xff0c;需下载pysocks包以设置反向代理&#xff08;具体可参考之前的笔记10&#xff09;&#xff1a; 再…