【设计模式深度剖析】【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 propertyint h = 127;//省略配置java.lang.Integer.IntegerCache.high参数改变最大值的设置的逻辑这里以默认的h=127为例high = h;cache = new Integer[(high - low) + 1];int j = low;//初始化缓存-128~127for(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;}@Overridepublic 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.rhkb.cn/news/338132.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【TB作品】MSP430G2553单片机,智能储物柜

智能储物柜将实现的功能&#xff1a; 1在超市或者机场场景下&#xff0c;用户需要进行物品暂存时。按下储物柜键盘的需求按键&#xff0c;智能储物柜将会随机为用户分配一个还没使用的柜子&#xff0c;屏幕提示用户选择密码存储方式或者身份证存储方式&#xff1b; 2 用户选择密…

教你搞一个比较简单的计时和进度条装饰器

教你搞一个比较简单的计时和进度条装饰器 什么是装饰器为啥要用装饰器呢&#xff1f;上代码&#xff01;如何使用装饰器效果 什么是装饰器 装饰器的英文是&#xff1a;Decorator。装修的英文是&#xff1a;Decoration。顾名思义就是我们要用装饰器在函数func()上搞点儿事儿&am…

纯Java实现Google地图的KMZ和KML文件的解析

目录 前言 一、关于KMZ和KML 1、KMZ是什么 2、KML是什么 二、Java解析实例 1、POM.xml引用 2、KML 基类定义 3、空间对象的定义 4、Kml解析工具类 三、KML文件的解析 1、KML解析测试 2、KMZ解析测试 四、总结 前言 今天是六.一儿童节&#xff0c;在这里祝各位大朋友…

oracle mysql索引区别

文章目录 1.引言1.1 索引的基本概念1.2 Oracle和MySQL的简介 2.Oracle索引2.1 Oracle索引的类型**B-Tree索引****Bitmap索引****Function-Based索引****Partitioned索引****Text索引** 2.2 Oracle索引的工作原理2.3 Oracle索引的实例代码 3.MySQL索引3.1 MySQL索引的类型**B-Tr…

synchronized 锁的到底是什么?

通过8种情况演示锁运行案例&#xff0c;看看我们到底锁的是什么 1锁相关的8种案例演示code package com.bilibili.juc.lock;import java.util.concurrent.TimeUnit;/*** 题目&#xff1a;谈谈你对多线程锁的理解&#xff0c;8锁案例说明* 口诀&#xff1a;线程 操作 资源类* 8…

CLIP--Learning Transferable Visual Models From Natural Language Supervision

参考&#xff1a;CLIP论文笔记--《Learning Transferable Visual Models From Natural Language Supervision》_visual n-grams模型-CSDN博客 openAI&#xff0c;2021&#xff0c;将图片和文字联系在一起&#xff0c;----->得到一个能非常好表达图片和文字的模型主题&#…

Pandas 使用 concat 数据合并你学会了吗?

1. 使用pd.concat()级联 pandas使用pd.concat函数&#xff0c;与np.concatenate函数类似 # 导包import numpy as npimport pandas as pd​# 为方便讲解&#xff0c;我们首先定义一个生成DataFrame的函数def make_df(indexs,columns): data [[str(j)str(i) for j in colum…

RabbitMQ延时队列

一、RabbitMQ下载并使用插件 1、查看RabbitMQ插件的文件路径 docker inspect rabbitmq 找到Mounts下面Name:rabbitmq_plugin的Source即为插件路径 使用 cd 进入到该目录 2、下载插件 wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download…

C# 使用Aspose生成和修改文档

Aspose库 C#中的Aspose库是一个强大的文件处理库&#xff0c;可以用于各种文件格式的创建、编辑、转换和操作。该库提供了丰富的功能&#xff0c;包括处理文档、电子表格、幻灯片、PDF、图像等多种文件格式&#xff0c;能够轻松实现文件的读取、写入、格式化、样式设置、数据操…

Java Apache Jaccard文本相似度匹配初体验

文章目录 前言一、文本相似度算法的选择二、常见的文本相似度算法介绍三、使用示例1、引入jar包2、方法示例3、Jaccard源码剖析4、Jaccard源码解释 写在最后 前言 产品今天提了个需求&#xff0c;大概是这样的&#xff0c;来&#xff0c;请看大屏幕。。。额。。。搞错了&#…

三丰云评测:免费虚拟主机和免费云服务器体验

今天我来为大家分享一下我的三丰云评测体验。三丰云是一家提供免费虚拟主机和免费云服务器的服务商&#xff0c;为了方便大家了解他们的服务&#xff0c;我特地注册了他们的免费虚拟主机和免费云服务器进行试用。在实际体验中&#xff0c;我发现三丰云的服务表现非常出色。首先…

【产品经理】总篇章

引言: 在最近频繁的产品职位面试中&#xff0c;我深刻体会到了作为产品需要的不仅仅是对市场和技术的敏锐洞察&#xff0c;更多的是在复杂多变的环境中&#xff0c;如何运用沟通、领导力和决策能力来引导产品从概念走向市场。这一系列博客将分享我多年经历和所学到的所以知识&a…

OpenCV学习 基础图像操作(十六):图像距离变换

基础原理 顾名思义&#xff0c;我们可以利用像素之间的距离作为对该像素的一种刻画&#xff0c;并将其运用到相应的计算之中。然而&#xff0c;在一幅图像之中&#xff0c;某种类型的像素并不是唯一的&#xff0c;因此我门常计算的是一类像素到另一类的最小距离&#xff0c;并…

工厂模式详情

一.介绍工厂模式的用途与特点 工厂方法模式是一种创建型设计模式&#xff0c; 其在父类中提供一个创建对象的方法&#xff0c; 允许子类决定实例化对象的类型。定义工厂方法模式(Fatory Method Pattern)是指定义一个创建对象的接口&#xff0c;但让实现这个接口的类来决定实例…

npm install node-sass 安装失败的解决方案:利用国内镜像加速安装

在开发前端项目时&#xff0c;使用Sass作为CSS预处理器是很多开发者的选择。然而&#xff0c;在通过npm安装其Node.js绑定库node-sass时&#xff0c;一些开发者可能会遇到安装失败的问题&#xff0c;尤其是网络原因导致的下载缓慢或中断。本文将指导你如何通过更换为国内镜像源…

如何在测试/线上环境页面访问本地接口?

文章目录 一、前言二、分析三、搭建1、搭建nginx&#xff0c;监听http请求转发2、监听https请求转发 四、总结 一、前言 在工作中&#xff0c;开发完的接口&#xff0c;一般测试的话&#xff0c;基本是使用Postman&#xff0c;如果要到页面测试&#xff0c;就要发版进行测试&a…

《逆水寒》手游周年庆,热度不减反增引发热议

易采游戏网5月31日最新消息&#xff1a;随着数字娱乐时代的飞速发展&#xff0c;手游市场的竞争愈发激烈。在这样的大背景下&#xff0c;《逆水寒》手游以其独特的古风武侠世界和深度的社交体验&#xff0c;自上线以来便吸引了无数玩家的目光。如今&#xff0c;这款游戏迎来了它…

知识运维概述

文章目录 知识运维研究现状技术发展趋势 知识运维 由于构建全量的行业知识图谱成本很高&#xff0c;在真实的场景落地过程中&#xff0c;一般遵循小步快走、快速迭代的原则进行知识图谱的构建和逐步演化。知识运维是指在知识图谱初次构建完成之后&#xff0c;根据用户的使用反馈…

WSL2-Ubuntu22.04-配置

WSL2-Ubuntu22.04-配置 准备1. WSL相关命令[^1]2. WSL2-Ubuntu22.04可视化3. WSL2 设置 CUDA4. 设置OpenGL 本文介绍了WSL2的基本使用方法及可视化&#xff0c;着重介绍了GPU和OpenGL的设置。 准备 名称版本windows11wsl2CUDA12.5 1. WSL相关命令1 查看已安装的wsl distribut…

DevExpress开发WPF应用实现对话框总结

说明&#xff1a; 完整代码Github​&#xff08;https://github.com/VinciYan/DXMessageBoxDemos.git&#xff09;DevExpree v23.2.4&#xff08;链接&#xff1a;https://pan.baidu.com/s/1eGWwCKAr8lJ_PBWZ_R6SkQ?pwd9jwc 提取码&#xff1a;9jwc&#xff09;使用Visual St…