【设计模式深度剖析】【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
类是客户端,它使用享元工厂获取享元对象,并传递外部状态给享元对象进行操作。
享元模式的优缺点
优点:
- 减少内存占用:通过复用对象实例,享元模式能够显著减少系统中对象的数量,从而节省内存空间。
- 提高性能:由于减少了对象的创建和销毁,享元模式能够提升应用程序的性能,特别是在处理大量相似对象时。
- 更易于管理:享元工厂集中管理了享元对象,这使得对象的创建和访问更加统一和可控。
缺点:
- 增加系统复杂性:享元模式要求区分内部状态和外部状态,这可能会增加系统的复杂性。开发人员需要仔细设计以确保正确实现享元模式。
- 可能增加代码量:为了实现享元模式和区分状态,可能需要编写更多的代码来管理享元对象和外部状态。
- 不适合所有情况:享元模式主要适用于处理大量相似对象的情况。如果系统中对象数量较少,或者对象之间的差异很大,那么使用享元模式可能并不合适。
使用场景
享元模式通常用于以下场景:
- 大量相似对象:当系统中存在大量相似对象,且这些对象的内部状态可以共享时,可以使用享元模式来复用这些对象,以减少内存占用和提高性能。
- 频繁创建和销毁对象:如果系统中频繁创建和销毁大量相似对象,使用享元模式可以显著减少对象的创建和销毁次数,从而提高性能。
- 需要集中管理对象:当需要集中管理对象的创建和访问时,可以使用享元工厂来统一管理和控制享元对象的创建和访问。
Java类库说明
Integer类
在Java类库中,虽然没有一个直接对应享元模式的类,但有一些设计思想或实现与享元模式相似。不过,为了更直接地说明享元模式在Java类库中的应用,我们可以参考java.lang.Integer
类的缓存机制,它使用了类似享元模式的思想来缓存一定范围内的整数对象。
Java的自动装箱(autoboxing)和拆箱(unboxing)特性使得我们可以在整数和Integer
对象之间自动转换。为了提高性能和减少内存使用,Java为-128
到127
之间的整数创建了一个对象缓存池。当我们在这个范围内进行装箱操作时(如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
对象。在静态初始化块中,-128
到127
之间的整数被预先创建并存储在cache
数组中。valueOf(int i)
方法用于返回指定整数值的Integer
实例。如果整数在缓存范围内,它直接从缓存中返回对应的对象;否则,它创建一个新的Integer
对象。这种设计思想与享元模式非常相似,因为它通过复用对象来减少内存使用和提高性能。然而,需要注意的是,
Integer
的缓存机制并不是完整的享元模式实现,因为它没有显式的享元工厂来管理和控制对象的创建。但是,它的设计思想和目的与享元模式是一致的。
String类
Java的String
类库没有直接体现享元模式(Flyweight Pattern)的设计,但它采用了一些机制来优化字符串的使用,以减少内存消耗和提高性能。这些机制虽然与享元模式的思想有相似之处,但并不完全符合享元模式的定义。
以下是String
类库中与性能优化和内存使用相关的几个关键点:
- 字符串不可变性(Immutability):Java中的
String
对象是不可变的,这意味着一旦创建了一个字符串对象,它的内容就不能再被修改。这种不可变性带来了很多好处,其中之一就是可以安全地在多个地方共享相同的字符串实例,而不用担心它被意外修改。然而,这并不意味着String
类本身使用了享元模式来复用字符串对象。 - 字符串常量池(String Constant Pool):在Java中,使用字面值创建的字符串(例如
"hello"
)会被自动放入一个称为“字符串常量池”的特殊内存区域中。如果后续代码中再次使用相同的字面值创建字符串,JVM会检查常量池中是否已经存在相同的字符串,如果存在则直接返回对该字符串的引用,而不是创建一个新的对象。这种机制可以减少不必要的字符串对象创建,从而节省内存和提高性能。然而,这种机制并不是由String
类本身实现的,而是由JVM在运行时管理的。 intern()
方法:String
类提供了一个intern()
方法,该方法可以将一个字符串添加到字符串常量池中(如果该字符串尚未存在于池中)。如果池中已经存在具有相同内容的字符串,则该方法返回对该字符串的引用;否则,它将在池中创建一个新的字符串对象,并返回对该对象的引用。这种方法提供了一种在运行时动态地将字符串添加到常量池中的机制,从而可以利用常量池的优化效果。但是,这并不意味着String
类使用了享元模式来管理其对象。
虽然String
类库中的这些机制与享元模式的思想有相似之处(即复用对象以减少内存使用和提高性能),但它们并不完全符合享元模式的定义。享元模式通常涉及一个显式的享元工厂类来管理和控制对象的创建和访问,而String
类库并没有这样的工厂类。此外,享元模式通常用于处理大量具有相同或相似状态的对象,而String
对象通常具有不同的内容(即不同的内部状态)。因此,虽然String
类库中的优化机制与享元模式有相似之处,但它们并不完全相同。
示例解析:GUI中的按钮
在GUI应用程序中,我们可以通过以下方式应用享元模式:
- 定义享元接口:创建一个表示按钮的接口,定义按钮的基本操作(如点击事件处理函数、设置样式等)。
- 实现具体享元:实现享元接口,定义按钮的内部状态,并提供操作这些状态的方法。
- 创建享元工厂:创建一个享元工厂类,用于创建和管理按钮对象。享元工厂应该提供一个方法来获取按钮对象,该方法接受一些参数(如按钮的样式、点击事件处理函数等)来指定按钮的内部状态。如果工厂中已经存在具有相同内部状态的按钮对象,则返回该对象的引用;否则,创建一个新的按钮对象并存储在工厂中。
- 在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类库设计加深理解
👈️上一篇:外观模式 | 下一篇:结构型设计模式对比👉️ 设计模式-专栏👈️ 目录 享元模式定义英文原话直译如何理解?字面理解例子:高脚杯的重复使用例子:GUI中的按钮传统方式使用享元模式 4个角色1. …...

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

麒麟系统 安装xrdp 远程桌面方法记录
一、安装环境 麒麟V10 2107 ft2000 麒麟V10 2107 x86_64 二、安装准备 使用《Kylin-Desktop-V10-Release-2107-arm64.iso》镜像 做好U盘启动系统后,需要安装一个远程桌面工具,可以多用户在windows上使用远程桌面访问麒麟系统。 目前在linux系统上较…...
解析Java中1000个常用类:SafeVarargs类,你学会了吗?
在 Java 编程中,泛型和可变参数(varargs)的结合使用可能会导致一些类型安全的问题。为了解决这些问题,Java 提供了 @SafeVarargs 注解。本文将详细介绍 @SafeVarargs 注解的定义、使用方法、应用场景以及其背后的原理,帮助读者深入理解并掌握这一重要特性。 什么是 @Safe…...

【数据挖掘】3σ原则识别数据中的异常值(附代码)
写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 路虽远,行则将至&#…...
人眼是如何看到物体的
我在试图理解人眼如何观察到物体的,发现没有解释。本来我想这应该跟照相机照相的结果一样,但是发现,照相机也不对劲,没有理由能成像啊。 因为物体在散射光的时候,假设散射的光在局部是平行光,那么物体散射…...

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

预编码算法(个人总结)
引言 预编码算法是现代无线通信系统中的关键技术,特别是在多输入多输出(MIMO)系统中。它们通过在发送端对信号进行处理,减少干扰并提高信道容量。这种技术广泛应用于5G、Wi-Fi和卫星通信系统中。本教程将详细介绍预编码算法的背景…...
【重学C语言】十七、预处理指令
【重学C语言】十七、预处理指令 预处理指令预定义宏`#define` 宏定义示例注意事项特殊符号条件编译头文件包含`#pragma`预处理指令 C语言中的预处理指令(Preprocessor Directives)是一种特殊的指令,它们在编译过程的早期阶段(即实际编译之前)被预处理器(Preprocessor)处…...
SQL注入的危害和原理
在Web应用开发中,SQL注入是一种常见的安全漏洞,它允许攻击者通过注入恶意的SQL语句来执行非法操作,甚至获取敏感数据。本篇博客将详细解释SQL注入的危害和原理,并提供一些解决方案,以帮助新人快速理解并避免这种安全威…...

Unity2D横版摄像机跟随
在Unity2D横版游戏中,摄像机跟随是一个非常重要的功能。一个流畅的摄像机跟随系统可以让玩家更好地沉浸在游戏世界中。本文将介绍如何在Unity中实现2D横版摄像机跟随,并分享一些优化技巧。 一、准备工作 在开始实现摄像机跟随之前,请确保您…...
Practicing Version Control
Part A 新建一个文件夹Git,然后进入文件夹: Windows: 在这个文件夹中右键 > Open Git Bash hereMac: 运行终端,打cd 空格,然后将文件夹拖入终端,按 return 从 Github 上复制 HTTPS,然后 git clone https://githu…...

新宏观范式和产业趋势下,纷享销客如何助力企业出海?
出海,已不再是企业的“备胎”,而是必须面对的“大考”!在这个全球化的大潮中,有的企业乘风破浪,勇攀高峰,也有的企业在异国他乡遭遇了“水土不服”。 面对“要么出海,要么出局”的抉择ÿ…...

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

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

数据结构——经典链表OJ(二)
乐观学习,乐观生活,才能不断前进啊!!! 我的主页:optimistic_chen 我的专栏:c语言 点击主页:optimistic_chen和专栏:c语言, 创作不易,大佬们点赞鼓…...

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

单实例11.2.0.3迁移到RAC11.2.0.4_使用RMAN 异机恢复
保命法则:先备份再操作,磁盘空间紧张无法备份就让满足,给自己留退路。 场景说明: 1.本文档的环境为同平台、不同版本(操作系统版本可以不同,数据库小版本不同),源机器和目标机器部…...
JavaScript第四讲:函数,作用域,运算符
前言 在JavaScript的广阔天地中,函数、作用域、算术运算符和逻辑运算符是构成代码世界的基石。它们各自扮演着不同的角色,却又紧密相连,共同编织出丰富多彩的程序逻辑。无论是编写一个简单的网页交互,还是构建一个复杂的应用程序…...

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

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...