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

news2024/11/20 3:27:57

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

设计模式-专栏👈️

目录

  • 享元模式
  • 定义
    • 英文原话
    • 直译
    • 如何理解?
      • 字面理解
      • 例子:高脚杯的重复使用
      • 例子: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只获取到一个对象实例)

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

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

返回


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

设计模式-专栏👈️

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

api网关kong对高频的慢接口进行熔断

一、背景 在生产环境&#xff0c;后端服务的接口响应非常慢&#xff0c;是因为数据库未创建索引导致。 如果QPS低的时候&#xff0c;因为后端服务有6个高配置的节点&#xff0c;虽然接口慢&#xff0c;还未影响到服务的正常运行。 但是&#xff0c;当QPS很高的时候&#xff0c…

.NET IoC 容器(三)Autofac

目录 .NET IoC 容器&#xff08;三&#xff09;AutofacAutofacNuget 安装实现DI定义接口定义实现类依赖注入 注入方式构造函数注入 | 属性注入 | 方法注入注入实现 接口注册重复注册指定参数注册 生命周期默认生命周期单例生命周期每个周期范围一个生命周期 依赖配置Nuget配置文…

07-操作元素(键盘和鼠标事件)

在前面的文章中重点介绍了一些元素的定位方法&#xff0c;定位到元素后&#xff0c;就需要操作元素了。本篇总结了web页面常用的一些操作元素方法&#xff0c;可以统称为行为事件。 一、简单操作 点击按钮&#xff08;鼠标左键&#xff09;&#xff1a;click()清空输入框&…

Linux静态库与动态库加载

了解库&#xff1a; 关于库相比大家之前肯定使用过&#xff0c;比如C/C里面的标准库&#xff0c;STL里面的各种库&#xff0c;我们在调用STL里的容器时都需要使用库&#xff0c;那么库到底是什么呢&#xff1f; 库的本质就是可执行程序的"半成品" 我们先来回顾一下代…

原生APP开发和Flutter开发的比较

原生APP开发和Flutter开发各有优缺点&#xff0c;适用于不同的场景和需求。下面是两者的详细比较&#xff0c;从开发语言、性能、开发效率、维护和更新、社区和支持等多个方面进行分析。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。…

工业安全智勇较量,赛宁网安工业靶场决胜工业网络攻防对抗新战场

2024年1月30日&#xff0c;工信部发布《工业控制系统网络安全防护指南》&#xff08;工信部网安〔2024〕14号&#xff09;&#xff0c;围绕安全管理、技术防护、安全运营、责任落实四方面提出安全防护要求&#xff0c;强调聚焦安全薄弱关键环节&#xff0c;强化技术应对策略&am…

C语言序列化和反序列化--TPL中的API(三)

tpl_map 创建tpl的唯一方法是调用tpl_map()。第一个参数是格式字符串。后面是格式字符串中特定字符所需的参数列表。例如, tpl_node *tn; int i; tn tpl_map( "A(i)", &i );该函数在格式字符串中的项和给定地址的C程序变量之间创建映射。稍后&#xff0c;C变量…

C. Turtle and an Incomplete Sequence

思路&#xff1a;首先如果都是-1的话&#xff0c;我们可以输出1 2循环&#xff0c;否则。 首先处理首尾的连续-1串&#xff0c;这个也很好处理&#xff0c;最后我们处理两个非-1之间的-1串。 对于左边的数a[l]和右边的数a[r]&#xff1a; 如果a[l] > a[r]&#xff0c;那么…

python移动文件

测试1(直接把B文件夹移动到了A里&#xff0c;成为了A的子文件夹) import os import shutil# 移动文件夹,B文件夹在当前目录没有了&#xff0c;跑到了A的子文件里 ## shutil.move(./example1/B/, ./example1/A/)测试2(B文件不动&#xff0c;将B文件里的所有的子文件夹移动到A内…

【算法】模拟算法——提莫攻击(easy)

题解&#xff1a;提莫攻击(模拟算法) 目录 1.题目2.题解3.参考代码4.总结 1.题目 题目链接&#xff1a;LINK 2.题解 举例&#xff1a; 3.参考代码 class Solution { public:int findPoisonedDuration(vector<int>& timeSeries, int duration) {int n timeSeri…

Java设计模式 _行为型模式_访问者模式

一、访问者模式 1、访问者模式 访问者模式&#xff08;Visitor Pattern&#xff09;是一种行为型模式。它允许在不修改已有类结构的情况下&#xff0c;向类中添加新的操作。访问者模式通过将操作封装在一个访问者对象中&#xff0c;使得可以在不改变各个元素类的前提下&#x…

thinkphp6 queue队列的maxTries自定义

前景需求&#xff1a;在我们用队列的时候发现maxtries的个数时255次&#xff0c;这个太影响其他队列任务 我目前使用的thinkphp版本是6.1 第一部定义一个新的类 CustomDataBase&#xff08;我用的mysql数据库存放的队列&#xff09; 重写__make 和createPlainPayload方法 …

《面试笔记》——MySQL终结篇30

三大范式&#xff1f; 第一范式&#xff1a;字段具有原子性&#xff0c;不可再分&#xff08;字段单一职责&#xff09; 第二范式&#xff1a;满足第一范式&#xff0c;每行应该被唯一区分&#xff0c;加一列存放每行的唯一标识符&#xff0c;称为主键&#xff08;都要依赖主…

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

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

Linux主机安全可视化运维(免费方案)

本文介绍如何使用免费的主机安全软件,在自有机房或企业网络实现对Linux系统进行可视化“主机安全”管理。 一、适用对象 本文适用于个人或企业内的Linux服务器运维场景,实现免费、高效、可视化的主机安全管理。提前发现主机存在的安全风险,全方位实时监控主机运行时入侵事…

如何设置eclipse中web.xml 文件的地址

新学了一个项目 &#xff0c;项目结构与平常自己构建的web项目不同 &#xff0c;用eclipse打开之后&#xff0c;eclipse竟然自己创建了一个web.xml 而项目里面原本的web.xml 文件eclipse没有识别出来&#xff0c;导致后来浏览器访问任何路径都报错404 一、修改项目中web.xml的…

Centos7.9环境下安装Keepalived(亲测版)

目录 一、在线安装 二、离线安装 (1)、 下载 (2)、安装依赖包 (3)、解压文件 (4)、编译 (4.1)、进入 keepalived-2.2.8 目录中 (4.2)、安装Keepalived (5)、配置文件修改 (6)、启动 (7)、检查启动状态 (8)、 设置开机自启 (9)、配置从节点 (10)、启动从节点keepalived…

ArcGIS教程(05):计算服务区和创建 OD 成本矩阵

准备视图 启动【ArcMap】->双击打开【Exercise05.mxd】->启用【Network Analyst 扩展模块】。前面的文章已经讲过&#xff0c;这里不再赘述。 创建服务区分析图层 1、在 Network Analyst 工具栏上&#xff0c;单击 【Network Analyst】&#xff0c;然后单击【新建服务…

el-table的一些操作

1.el-table实现全部选择和全部取消 其实非常简单&#xff0c;el-table自带的都有方法toggleAllSelection()和clearSelection() 表格数据&#xff1a; <el-button clickcheckAll>全选</el-button> <el-button clickcancelAll>反选</el-button>// 全…

每日一练编程题:今天是【接口,多态】

设计程序 : 电脑类的属性USB接口数组 : 有3个usb插口电脑类的功能 : 通过接口插入外设 (u盘,麦克风,键盘等) addUSB(USB usb) { }开机 要求: 电脑开机前,先启动外设关机 要求: 电脑关机前,先关闭外设 外设类(u盘,麦克风,键盘等) 功能 : 启动 关闭 USB接口 定义usb设备的统一…