当前位置: 首页 > news >正文

【设计模式深度剖析】【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只获取到一个对象实例)

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

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

返回


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

设计模式-专栏👈️

相关文章:

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

&#x1f448;️上一篇:外观模式 | 下一篇:结构型设计模式对比&#x1f449;️ 设计模式-专栏&#x1f448;️ 目录 享元模式定义英文原话直译如何理解&#xff1f;字面理解例子&#xff1a;高脚杯的重复使用例子&#xff1a;GUI中的按钮传统方式使用享元模式 4个角色1. …...

OceanBase 内存研究(OceanBase 3.2.4.5)

内存结构 从官网的结构图可以看出&#xff0c;一台observer可使用的总内存(memory_limit)包括 系统内存(system_memory) 和 租户内存(sys租户与普通租户) 系统内存 系统内存system_memory 属于 observer 的内部内存&#xff0c;允许其它租户共享使用该内存资源 (root10.0.0.…...

麒麟系统 安装xrdp 远程桌面方法记录

一、安装环境 麒麟V10 2107 ft2000 麒麟V10 2107 x86_64 二、安装准备 使用《Kylin-Desktop-V10-Release-2107-arm64.iso》镜像 做好U盘启动系统后&#xff0c;需要安装一个远程桌面工具&#xff0c;可以多用户在windows上使用远程桌面访问麒麟系统。 目前在linux系统上较…...

解析Java中1000个常用类:SafeVarargs类,你学会了吗?

在 Java 编程中,泛型和可变参数(varargs)的结合使用可能会导致一些类型安全的问题。为了解决这些问题,Java 提供了 @SafeVarargs 注解。本文将详细介绍 @SafeVarargs 注解的定义、使用方法、应用场景以及其背后的原理,帮助读者深入理解并掌握这一重要特性。 什么是 @Safe…...

【数据挖掘】3σ原则识别数据中的异常值(附代码)

写在前面&#xff1a; 首先感谢兄弟们的订阅&#xff0c;让我有创作的动力&#xff0c;在创作过程我会尽最大能力&#xff0c;保证作品的质量&#xff0c;如果有问题&#xff0c;可以私信我&#xff0c;让我们携手共进&#xff0c;共创辉煌。 路虽远&#xff0c;行则将至&#…...

人眼是如何看到物体的

我在试图理解人眼如何观察到物体的&#xff0c;发现没有解释。本来我想这应该跟照相机照相的结果一样&#xff0c;但是发现&#xff0c;照相机也不对劲&#xff0c;没有理由能成像啊。 因为物体在散射光的时候&#xff0c;假设散射的光在局部是平行光&#xff0c;那么物体散射…...

vue打包时报错文件包过大

1.问题&#xff1a;npm run build 之后出现 2. 翻译之后意思就是某块过大 3. 解决办法&#xff1a;在vite.config.ts文件上添加 build: { chunkSizeWarningLimit: 1600, }, 4.最终打包...

预编码算法(个人总结)

引言 预编码算法是现代无线通信系统中的关键技术&#xff0c;特别是在多输入多输出&#xff08;MIMO&#xff09;系统中。它们通过在发送端对信号进行处理&#xff0c;减少干扰并提高信道容量。这种技术广泛应用于5G、Wi-Fi和卫星通信系统中。本教程将详细介绍预编码算法的背景…...

【重学C语言】十七、预处理指令

【重学C语言】十七、预处理指令 预处理指令预定义宏`#define` 宏定义示例注意事项特殊符号条件编译头文件包含`#pragma`预处理指令 C语言中的预处理指令(Preprocessor Directives)是一种特殊的指令,它们在编译过程的早期阶段(即实际编译之前)被预处理器(Preprocessor)处…...

SQL注入的危害和原理

在Web应用开发中&#xff0c;SQL注入是一种常见的安全漏洞&#xff0c;它允许攻击者通过注入恶意的SQL语句来执行非法操作&#xff0c;甚至获取敏感数据。本篇博客将详细解释SQL注入的危害和原理&#xff0c;并提供一些解决方案&#xff0c;以帮助新人快速理解并避免这种安全威…...

Unity2D横版摄像机跟随

在Unity2D横版游戏中&#xff0c;摄像机跟随是一个非常重要的功能。一个流畅的摄像机跟随系统可以让玩家更好地沉浸在游戏世界中。本文将介绍如何在Unity中实现2D横版摄像机跟随&#xff0c;并分享一些优化技巧。 一、准备工作 在开始实现摄像机跟随之前&#xff0c;请确保您…...

Practicing Version Control

Part A 新建一个文件夹Git&#xff0c;然后进入文件夹: Windows: 在这个文件夹中右键 > Open Git Bash hereMac: 运行终端&#xff0c;打cd 空格&#xff0c;然后将文件夹拖入终端&#xff0c;按 return 从 Github 上复制 HTTPS&#xff0c;然后 git clone https://githu…...

新宏观范式和产业趋势下,纷享销客如何助力企业出海?

出海&#xff0c;已不再是企业的“备胎”&#xff0c;而是必须面对的“大考”&#xff01;在这个全球化的大潮中&#xff0c;有的企业乘风破浪&#xff0c;勇攀高峰&#xff0c;也有的企业在异国他乡遭遇了“水土不服”。 面对“要么出海&#xff0c;要么出局”的抉择&#xff…...

安装zookeeper

一、搭建前准备 192.168.1.99 sdw1 192.168.1.98 sdw2 192.168.1.97 sdw3 二、搭建 1、各主机修改/etc/hosts&#xff0c;/etc/hostname文件 /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhos…...

深入分析 Android Activity (二)

文章目录 深入分析 Android Activity (二)1. Activity 的启动模式&#xff08;Launch Modes&#xff09;1.1 标准模式&#xff08;standard&#xff09;1.2 单顶模式&#xff08;singleTop&#xff09;1.3 单任务模式&#xff08;singleTask&#xff09;1.4 单实例模式&#xf…...

数据结构——经典链表OJ(二)

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 点击主页&#xff1a;optimistic_chen和专栏&#xff1a;c语言&#xff0c; 创作不易&#xff0c;大佬们点赞鼓…...

文件IO(三)

文件IO&#xff08;三&#xff09; 左移右移Linux的man 手册文件IO打开文件操作文件关闭文件 caps lock开灯关灯读取按键文件IO操作目录文件打开目录文件操作目录文件 库动态库和静态库的优缺点创建静态库创建动态库 按下右ctrl键 亮灭灯 左移右移 Linux的man 手册 文件IO 打开…...

单实例11.2.0.3迁移到RAC11.2.0.4_使用RMAN 异机恢复

保命法则&#xff1a;先备份再操作&#xff0c;磁盘空间紧张无法备份就让满足&#xff0c;给自己留退路。 场景说明&#xff1a; 1.本文档的环境为同平台、不同版本&#xff08;操作系统版本可以不同&#xff0c;数据库小版本不同&#xff09;&#xff0c;源机器和目标机器部…...

JavaScript第四讲:函数,作用域,运算符

前言 在JavaScript的广阔天地中&#xff0c;函数、作用域、算术运算符和逻辑运算符是构成代码世界的基石。它们各自扮演着不同的角色&#xff0c;却又紧密相连&#xff0c;共同编织出丰富多彩的程序逻辑。无论是编写一个简单的网页交互&#xff0c;还是构建一个复杂的应用程序…...

IDEA中,MybatisPlus整合Spring项目的基础用法

一、本文涉及的知识点【重点】 IDEA中使用MybatisPlus生成代码&#xff0c;并使用。 Spring整合了Mybatis框架后&#xff0c;开发变得方便了很多&#xff0c;然而&#xff0c;Mapper、Service和XML文件&#xff0c;在Spring开发中常常会重复地使用&#xff0c;每一次的创建、修…...

从不同角度看如何让大模型变得更聪明呢?

算法创新&#xff0c;从代码上优化大模型&#xff0c;可以采取一系列策略来提升其性能和效率。 算法优化&#xff1a;对模型的算法进行精细调整&#xff0c;如改进神经网络架构&#xff0c;使用更高效的层&#xff08;如深度可分离卷积&#xff09;&#xff0c;或者优化递归神经…...

Buffer Pool运行机制理解

Buffer Pool机制理解 一、为什么使用Buffer Pool&#xff1f; 众所周知&#xff0c;磁盘数据是以数据页的形式来去读取的&#xff0c;一个数据页默认大小 16K&#xff0c;也就是说你本意只想读取一行数据&#xff0c;但是它会给你加载一页的数据到buffer pool里面。这样的话就…...

windows配置dns访问git , 加快访问速度保姆级教程

设置 DNS 访问 Git 需要修改电脑的 DNS 配置。下面是具体的操作流程&#xff1a; 第一步&#xff1a;打开命令提示符或终端窗口 在 Windows 系统中&#xff0c;可以按下 Win R 组合键&#xff0c;然后输入 “cmd”&#xff0c;按下 Enter 键打开命令提示符窗口。在 macOS 或 …...

Solidity学习-投票合约示例

以下的合约有一些复杂&#xff0c;但展示了很多Solidity的语言特性。它实现了一个投票合约。 当然&#xff0c;电子投票的主要问题是如何将投票权分配给正确的人员以及如何防止被操纵。 我们不会在这里解决所有的问题&#xff0c;但至少我们会展示如何进行委托投票&#xff0c;…...

前端Vue自定义支付密码输入框键盘与设置弹框组件的设计与实现

摘要 随着信息技术的不断发展&#xff0c;前端开发的复杂性日益加剧。传统的开发方式&#xff0c;即将整个系统构建为一个庞大的整体应用&#xff0c;往往会导致开发效率低下和维护成本高昂。任何微小的改动或新功能的增加都可能引发对整个应用逻辑的广泛影响&#xff0c;这种…...

【QEMU中文文档】1.1 支持的构建平台

本文由 AI 翻译&#xff08;ChatGPT-4&#xff09;完成&#xff0c;并由作者进行人工校对。如有任何问题或建议&#xff0c;欢迎联系我。联系方式&#xff1a;jelin-shoutlook.com。 原文&#xff1a;Supported build platforms — QEMU documentation QEMU 旨在支持在多个主机…...

摄影后期照片编辑工具:LrC2024 for Mac/win 中文激活版

LrC2024&#xff08;Lightroom Classic 2024&#xff09;是 Adobe 公司推出的一款专业级别的照片编辑和管理软件。它是 Lightroom Classic CC 的升级版&#xff0c;具有更多的功能和改进。 这款软件主要用于数字摄影师和摄影爱好者处理、编辑和管理他们的照片。它提供了一套强大…...

通关!游戏设计之道Day20

用时20天&#xff0c;《通关&#xff01;游戏设计之道》也是完结撒花喽。 虽然只是浅显的读了一遍但收获还是很多的。我想在我真正开始做游戏时再回来看&#xff0c;一定会收获更多的。 《通关游戏设计之道》是一本深入探讨游戏设计的专业书籍&#xff0c;它不仅仅是一本理论…...

2024年上半年软件设计师试题及答案(回忆版)--选择题

基础知识选择题 基础知识选择题 1,2,3][4,5,6][1,2,3,4,5,6] &#xff08;总&#xff1a;1分&#xff09; &#xff08;注意&#xff1a;括号内的是截止当前题目总分&#xff09; vlan不能隔绝内外网 &#xff08;2分&#xff09; 链路层使用交换机&#xff0c;…...

5.28.1 使用卷积神经网络检测乳腺癌

深度学习技术正在彻底改变医学图像分析领域&#xff0c;因此在本研究中&#xff0c;我们提出了卷积神经网络 (CNN) 用于乳腺肿块检测&#xff0c;以最大限度地减少手动分析的开销。CNN 架构专为特征提取阶段而设计&#xff0c;并采用了更快的 R-CNN 的区域提议网络 (RPN) 和感兴…...

【JavaScript脚本宇宙】JavaScript日期处理神器: 6款顶级库解析

提升编程效率&#xff1a;六个强大的JavaScript日期时间库介绍 前言 在信息化社会&#xff0c;日期和时间的处理是任何编程语言必不可少的部分。本文将介绍六个优秀的JavaScript日期和时间库&#xff0c;这些库各有特色&#xff0c;可以应对多样的使用场景。 欢迎订阅专栏&am…...

C++基础编程100题-002 OpenJudge-1.1-04 输出保留3位小数的浮点数

更多资源请关注纽扣编程微信公众号 002 OpenJudge-1.1-04 输出保留3位小数的浮点数 http://noi.openjudge.cn/ch0101/04/ 描述 读入一个单精度浮点数&#xff0c;保留3位小数输出这个浮点数。 输入 只有一行&#xff0c;一个单精度浮点数。 输出 也只有一行&#xff0c;…...

Linux挂载硬盘

通过df -h命令后无硬盘信息&#xff0c;但是已经分配了硬盘&#xff0c;需要将硬盘挂载到主机上。 通过命令&#xff1a;lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sr0 11:0 1 492K 0 rom vda 252:0 0 50G 0 disk …...

用户购物性别模型标签(USG)之决策树模型

一、USG模型引入: 首先了解一下&#xff0c;如何通过大数据来确定用户的真实性别&#xff0c; 经常谈论的用户精细化运营&#xff0c;到底是什么? 简单来讲&#xff0c;就是将网站的每个用户标签化&#xff0c;制作一个属于用户自己的网络身份证。然后&#xff0c;运营人员 通…...

Mock的用法

1. 引入unittest包&#xff0c;再从包里引用mock类 import unittest from unittest import Mock 2. mock的作用&#xff0c;做挡板或者用来做一些单元测试过程中复杂的数据的模拟 demo Demo() #把mock的值赋值给demo的get()方法&#xff0c;这样在调用这个方法时&#xff0…...

内网-win1

一、概述 1、工作组&#xff1a;将不同的计算机按功能(或部门)分别列入不同的工作组 (1)、查看&#xff08;windows&#xff09; 查看当前系统中所有用户组&#xff1a;打开命令行--》net localgroup查看组中用户&#xff1a;打开命令行 --》net localgroup 后接组名查看用户…...

中国电子学会(CEIT)2023年09月真题C语言软件编程等级考试三级(含详细解析答案)

中国电子学会(CEIT)考评中心历届真题(含解析答案) C语言软件编程等级考试三级 2023年09月 编程题五道 总分:100分一、谁是你的潜在朋友(20分) "臭味相投"一这是我们描述朋友时喜欢用的词汇。两个人是朋友通常意味着他们存在着 许多共同的兴趣。然而作为…...

golang线程池ants-四种使用方法

目录 1、ants介绍 2、使用方式汇总 3、各种使用方式详解 3.1 默认池 3.2 普通模式 3.3 带参函数 3.4 多池多协程 4、总结 1、ants介绍 众所周知&#xff0c;goroutine相比于线程来说&#xff0c;更加轻量、资源占用更少、无线程上下文切换等优势&#xff0c;但是也不能…...

Flutter开发效率提升1000%,Flutter Quick教程之对组件进行拖拽与接收

1&#xff0c;首先&#xff0c;所有可以选择的组件&#xff0c;都在左边的组件面板里。从里面点击任何一个&#xff0c;按住左键&#xff0c;向右边的手机面板上进行拖拽即可。 2&#xff0c;拖拽后&#xff0c;我们要选择一个接收组件。什么时候可以接收组件&#xff0c;就是当…...

揭秘小程序商城的团购奇迹:独特模式引领盈利新纪元

在数字经济的新纪元里&#xff0c;你是否对那些不张扬却充满潜力的商业模式心生好奇&#xff1f;今天&#xff0c;我要为你揭示一种别出心裁的商业模式&#xff0c;它以其独特的魅力&#xff0c;不仅迅速吸引了大量用户的目光&#xff0c;更在短短一个月内创造了超过600万的惊人…...

ssm_mysql_高校自习室预约系统(源码)

博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…...

AI自动化办公:批量将Excel表格英文内容翻译为中文

有一个50列的表格&#xff0c;里面都是英文&#xff0c;要翻译成中文&#xff1a; 在ChatGPT中输入提示词&#xff1a; 你是一个开发AI大模型应用的Python编程专家&#xff0c;要完成以下任务的Python脚本&#xff1a; 打开Excel文件&#xff1a;"F:\AI自媒体内容\AI行业…...

PPT 隐藏开启对象图层

目录预览 一、问题描述二、解决方案三、参考链接 一、问题描述 制作PPT的时候&#xff0c;有时候需要在一张PPT放置多个依次出现的内容&#xff0c;然后设置对应的动画&#xff0c;要是需要对某个内容进行修改的话&#xff0c;就会很不方便&#xff0c;这个时候就需要使用&…...

PHP火狼大灌篮游戏源码微信+手机wap源码带控制

使用此接口可以实现支付宝、QQ钱包、微信支付与财付通的即时到账&#xff0c;免签约&#xff0c;无需企业认证。PHP易支付源码&#xff0c;免签约不需要企业的支付平台源码&#xff0c;彩虹第三四方在线支付系统源码,易支付token合作者商户申请源码&#xff0c;app和网页都可以…...

推荐几首听无数遍也听不腻的好歌(1)

1.Wannabe (Spice Girls Cover) 这首歌是Why Mona创作的首红眼特效的歌&#xff0c;唱的像牙痛的唱不清楚&#xff0c;但配上超级劲爆的旋律及节奏&#xff0c;简直好听到爆 2.Down For Life (Reset) 这首HSHK创作的纯音乐&#xff0c;虽然旋律一直重复一个调&#xff0c;但…...

【全开源】Java短剧系统微信小程序+H5+微信公众号+APP 源码

打造属于你的精彩短视频平台 一、引言&#xff1a;为何选择短剧系统小程序&#xff1f; 在当今数字化时代&#xff0c;短视频已经成为人们日常生活中不可或缺的一部分。而短剧系统小程序源码&#xff0c;作为构建短视频平台的强大工具&#xff0c;为广大开发者提供了快速搭建…...

基于Springboot驾校预约平台小程序的设计与实现(源码+数据库+文档)

一.项目介绍 系统角色&#xff1a;管理员、教练、学员 小程序(仅限于学员注册、登录)&#xff1a; 查看管理员发布的公告信息 查看管理员发布的驾校信息 查看所有教练信息、预约(需教练审核)、评论、收藏喜欢的教练 查看管理员发布的考试信息、预约考试(需管理…...

python列表基本运算

列表基本运算 成员运算符 in 老师你在上课喊人回答问题的时候&#xff0c;就犯了难。想点的人名字已经脱口而出了&#xff0c;但发现这位同学没来。 可不&#xff0c;今天的课就来了 8 个人&#xff1a; students [林黛玉, 薛宝钗, 贾元春, 妙玉, 贾惜春, 王熙凤, 秦可卿,…...

Pytorch实用教程:pytorch中nn.Linear()用法详解 | 构建多层感知机 | nn.Module的作用 | nn.Sequential的作用

文章目录 1. nn.Linear()用法构造函数参数示例使用场景2. 构建多层感知机步骤代码示例注意事项3. 继承自nn.Module的作用是什么?1. 组织网络结构2. 参数管理3. 模型保存和加载4. 设备管理不继承 `nn.Module` 的后果...

如何利用unicloud阿里云云函数实现文件包括图片或文件上传,unicloud云函数写法一览

这里以一个单文件上传为例子&#xff0c;多图多文件同理&#xff0c;循环单图处理逻辑即可。 背景 前端vue上传图片文件&#xff08;base64格式&#xff09;到服务器&#xff0c;并获取返回的服务器资源存储路径 传入参数 { ”queryStringParameters“:{ "file":&qu…...