设计模式-结构型模式:享元模式

简介: 设计模式-结构型模式:享元模式

1、简介

享元模式(Flyweight Pattern)是一种结构型设计模式,用于优化大量相似对象的内存使用

它通过共享对象来减少内存使用,从而提高程序的性能和效率。享元模式的核心思想是共享对象,也就是将相同或相似的对象共享一份内存空间。

2、🔺对象类型

在享元模式中,通常有两种对象类型:内部状态和外部状态。

内部状态是可以共享的,而外部状态是不可以共享的

内部状态是指在对象创建时确定并且不会改变的状态,外部状态是指在对象创建之后可能会改变的状态。

通过将相同的内部状态的对象缓存起来,减少了相同内部状态的对象的数量,从而达到减少内存的目的。当需要使用相同内部状态的对象时,只需要从缓存中取出即可。

享元模式的实现需要分离出对象的内部状态和外部状态,然后使用工厂模式来创建和管理对象。

此处为语雀内容卡片,点击链接查看:1、工厂方法 · 语雀

内部状态可以作为享元对象的属性,而外部状态则需要作为方法的参数传入。当需要创建对象时,先检查对象池中是否已经有对应内部状态的对象,如果有则直接返回,如果没有则创建新对象并将其添加到对象池中。在使用对象时,只需要传入外部状态,即可得到完整的对象。

2.1、享元之工厂模式

享元模式使用的是工厂方法模式,而不是抽象工厂模式。

工厂方法模式是一种创建型设计模式,用于定义一个创建对象的接口,但将具体的对象创建延迟到子类中实现。在享元模式中,我们使用了享元工厂类来管理共享对象的创建和缓存,这个工厂类就是工厂方法模式中的工厂。

具体地说,在享元模式中,我们定义了一个 Flyweight 接口,用于定义共享对象的接口;一个具体的享元类 ConcreteFlyweight,用于实现共享对象的具体操作;以及一个享元工厂类 FlyweightFactory,用于管理共享对象的创建和缓存。在享元工厂类中,我们使用工厂方法 getFlyweight 来创建共享对象。

相比之下,抽象工厂模式是一种创建型设计模式,用于定义一系列相关对象的接口,而不需要指定具体的类。在抽象工厂模式中,我们定义了一个抽象工厂类和一组抽象产品类,具体的工厂和产品类是通过实现这些抽象类来实现的。

因此,尽管享元模式和抽象工厂模式都涉及到对象的创建,但它们的目的和实现方式是不同的,因此享元模式使用的是工厂方法模式,而不是抽象工厂模式。

3、组成部分

享元模式包含以下组成部分:

  1. Flyweight(享元):是抽象享元类或者具体享元类的接口,通过这个接口可以接收外部状态并进行相应的处理。
  2. ConcreteFlyweight(具体享元类):是实现 Flyweight 接口的具体类,其中存储了享元的内部状态。
  3. UnsharedConcreteFlyweight(非共享具体享元类):是一些不需要共享的具体享元类,一般情况下很少使用。
  4. FlyweightFactory(享元工厂类):是负责创建和管理享元对象的工厂类,其中维护了一个享元池用于存储已经创建的享元对象,避免重复创建。
  5. Client(客户端):是调用享元对象的客户端代码,可以通过享元工厂获取已经创建的享元对象,并将外部状态传递给享元对象进行处理。

这些组成部分共同协作,实现了对象的共享和复用,从而降低了系统的内存消耗和运行时开销。

4、使用场景

享元模式是一种优化内存使用的设计模式,适用于以下场景:

  1. 对象数量大且相似:如果一个系统中有大量相似的对象需要被创建,而这些对象的内部状态可以共享,那么可以使用享元模式来减少内存的使用,提高程序的性能和效率。比如说,一个文本编辑器中需要创建大量的字符对象,这些字符对象有很多相同的属性,比如字体和颜色,可以使用享元模式来实现。
  2. 内部状态较少:如果一个对象的内部状态较少,那么使用享元模式可以有效地减少内存的使用。比如说,一个围棋游戏中的棋子,它们的内部状态只有颜色和形状,可以使用享元模式来减少内存的使用。
  3. 大对象:如果一个对象很大,而系统中需要创建大量的这种对象,那么可以使用享元模式来减少内存的使用。比如说,一个游戏中需要创建大量的怪物对象,而这些对象都包含了很多属性,可以使用享元模式来优化内存使用。
  4. 对象的状态可以外部化:如果一个对象的状态可以外部化,那么可以使用享元模式来减少内存的使用。比如说,一个网络聊天室中的用户对象,它们的状态可以外部化,比如说用户名、密码、头像等,可以使用享元模式来优化内存使用。
  5. 低延迟要求:如果一个系统需要实现低延迟的操作,那么可以使用享元模式来优化内存使用。由于享元对象已经被缓存起来了,因此可以快速地进行对象的创建和访问,从而实现低延迟的操作。

总之,如果一个系统中有大量相似的对象需要被创建,并且这些对象的内部状态可以共享,那么就可以使用享元模式来优化内存使用,提高程序的性能和效率。

5、优缺点

享元模式是一种优化内存使用的设计模式,它具有以下优点和缺点:

优点:

  1. 减少内存使用:使用享元模式可以减少系统中对象的数量,从而减少内存的使用,提高系统的性能和效率。
  2. 提高性能:由于享元对象已经被缓存起来了,因此可以快速地进行对象的创建和访问,从而提高系统的性能和效率。
  3. 提高可维护性:使用享元模式可以将对象的状态分为内部状态和外部状态,从而提高代码的可维护性。
  4. 可以实现对象池:使用享元模式可以实现对象池,从而实现对象的复用,提高系统的性能和效率。

缺点:

  1. 增加复杂度:使用享元模式需要将对象的状态分为内部状态和外部状态,这增加了代码的复杂度。
  2. 限制状态共享:使用享元模式需要将对象的状态分为内部状态和外部状态,如果对象的状态不能分为内部状态和外部状态,那么就不能使用享元模式。
  3. 破坏封装性:使用享元模式需要将对象的状态分为内部状态和外部状态,这可能会破坏对象的封装性。

总之,享元模式是一种优化内存使用的设计模式,它可以减少内存的使用,提高系统的性能和效率,但也有一些缺点,比如增加代码的复杂度、限制状态共享和破坏对象的封装性。在使用享元模式时,需要根据具体的情况权衡优缺点,选择合适的方案。

6、实现

6.1、介绍实现方法

在Java中,享元模式的实现通常使用了单例模式和哈希表来管理对象池。常见的应用场景包括字符串常量池、线程池和数据库连接池等。通过使用享元模式,可以有效地减少内存使用,提高程序性能和效率。

6.1.1、单例

此处为语雀内容卡片,点击链接查看:3、单例 · 语雀

享元模式的实现可以使用单例模式来保证享元工厂类的唯一性。

享元模式中的享元工厂类负责管理共享对象的创建和缓存,因此必须保证该类的唯一性,以避免创建多个实例而导致的资源浪费和数据不一致等问题。一种常用的实现方式是使用单例模式来实现享元工厂类。

在单例模式中,我们使用一个静态变量来保存该类的唯一实例,同时将构造函数设为私有,以避免外部代码创建新的实例。我们可以将享元工厂类的构造函数设为私有,然后提供一个静态方法来获取该类的唯一实例。这样,无论在何时何地调用该方法,都可以得到同一个享元工厂对象的实例。

6.1.2、常用的单例

(享元模式的实现可以使用任何一种单例模式,比较常用的是懒汉式单例模式和双重检查锁单例模式)

此处为语雀内容卡片,点击链接查看:②懒汉式单例 · 语雀

懒汉式单例模式是一种常用的单例模式,其特点是在首次调用 getInstance 方法时才创建唯一实例。在多线程环境下,懒汉式单例模式需要考虑线程安全性问题,可以通过 synchronized 关键字或者静态内部类等方式来解决。

以下是使用懒汉式单例模式实现享元工厂类的示例代码:

1. public class FlyweightFactory {
2. private static FlyweightFactory instance;
3. 
4. // 私有构造函数
5. private FlyweightFactory() {
6. // ...
7.     }
8. 
9. // 静态方法获取唯一实例
10. public static synchronized FlyweightFactory getInstance() {
11. if (instance == null) {
12.             instance = new FlyweightFactory();
13.         }
14. return instance;
15.     }
16. 
17. // 其他方法
18. // ...
19. }

此处为语雀内容卡片,点击链接查看:③双重检查锁单例 · 语雀

双重检查锁单例模式是一种更加高效的单例模式,其特点是通过双重检查来保证线程安全性和效率。在多线程环境下,双重检查锁单例模式可以避免每次调用 getInstance 方法时都加锁的开销,从而提高了程序的性能。

以下是使用双重检查锁单例模式实现享元工厂类的示例代码:

1. public class FlyweightFactory {
2. private static volatile FlyweightFactory instance;
3. 
4. // 私有构造函数
5. private FlyweightFactory() {
6. // ...
7.     }
8. 
9. // 静态方法获取唯一实例
10. public static FlyweightFactory getInstance() {
11. if (instance == null) {
12. synchronized (FlyweightFactory.class) {
13. if (instance == null) {
14.                     instance = new FlyweightFactory();
15.                 }
16.             }
17.         }
18. return instance;
19.     }
20. 
21. // 其他方法
22. // ...
23. }

在上述代码中,我们使用了 volatile 关键字来保证 instance 变量的可见性,避免出现多线程环境下的数据不一致问题。另外,我们使用了双重检查的方式来避免每次调用 getInstance 方法时都加锁的开销,提高了程序的性能。

6.2、demo代码实现

下面是一个简单的 Java 代码实现享元模式的示例:

首先,我们定义一个接口 Flyweight,用于定义共享对象的接口:

1. public interface Flyweight {
2. void operation(String extrinsicState);
3. }

然后,我们定义一个具体的享元类 ConcreteFlyweight,用于实现共享对象的具体操作:

1. public class ConcreteFlyweight implements Flyweight {
2. private String intrinsicState;
3. 
4. public ConcreteFlyweight(String intrinsicState) {
5. this.intrinsicState = intrinsicState;
6.     }
7. 
8. @Override
9. public void operation(String extrinsicState) {
10.         System.out.println("Intrinsic state: " + this.intrinsicState);
11.         System.out.println("Extrinsic state: " + extrinsicState);
12.     }
13. }

在具体的享元类中,我们定义了一个内部状态 intrinsicState,用于存储共享对象的内部状态。在 operation 方法中,我们输出了共享对象的内部状态和外部状态。

接下来,我们定义一个享元工厂类 FlyweightFactory,用于管理共享对象的创建和缓存:

1. public class FlyweightFactory {
2. private Map<String, Flyweight> flyweights = new HashMap<>();
3. 
4. public Flyweight getFlyweight(String intrinsicState) {
5. if (!flyweights.containsKey(intrinsicState)) {
6. Flyweight flyweight = new ConcreteFlyweight(intrinsicState);
7.             flyweights.put(intrinsicState, flyweight);
8.         }
9. return flyweights.get(intrinsicState);
10.     }
11. }

在享元工厂类中,我们定义了一个 flyweights 字典,用于存储共享对象。在 getFlyweight 方法中,我们首先判断字典中是否已经存在对应的共享对象,如果不存在,则创建一个新的共享对象并加入字典,如果存在,则直接返回字典中的共享对象。

最后,我们可以使用如下代码来测试享元模式的实现:

1. public class client {
2. public static void main(String[] args) {
3. FlyweightFactory factory = new FlyweightFactory();
4. Flyweight flyweight1 = factory.getFlyweight("state1");
5. Flyweight flyweight2 = factory.getFlyweight("state1");
6. Flyweight flyweight3 = factory.getFlyweight("state2");
7. 
8.         flyweight1.operation("external state 1");
9.         flyweight2.operation("external state 2");
10.         flyweight3.operation("external state 3");
11. 
12.     }
13. }

在测试代码中,我们首先创建了一个享元工厂类的实例 factory,然后使用 factory.getFlyweight 方法来获取共享对象。我们创建了两个状态相同的共享对象 flyweight1 和 flyweight2,以及一个状态不同的共享对象 flyweight3。最后,我们分别调用了这三个共享对象的 operation 方法,并传递了不同的外部状态。

当我们运行这段代码时,可以看到如下输出结果:

从输出结果可以看出,享元模式可以有效地共享对象,减少内存的使用,提高系统的性能和效率。

 

相关文章
|
3月前
|
设计模式 Java
Java设计模式-享元模式(12)
Java设计模式-享元模式(12)
|
4月前
|
设计模式 存储 Java
【十】设计模式~~~结构型模式~~~享元模式(Java)
文章详细介绍了享元模式(Flyweight Pattern),这是一种对象结构型模式,通过共享技术实现大量细粒度对象的重用,区分内部状态和外部状态来减少内存中对象的数量,提高系统性能。通过围棋棋子的设计案例,展示了享元模式的动机、定义、结构、优点、缺点以及适用场景,并探讨了单纯享元模式和复合享元模式以及与其他模式的联用。
【十】设计模式~~~结构型模式~~~享元模式(Java)
|
4月前
|
设计模式 存储 Java
【九】设计模式~~~结构型模式~~~外观模式(Java)
文章详细介绍了外观模式(Facade Pattern),这是一种对象结构型模式,通过引入一个外观类来简化客户端与多个子系统之间的交互,降低系统的耦合度,并提供一个统一的高层接口来使用子系统。通过文件加密模块的实例,展示了外观模式的动机、定义、结构、优点、缺点以及适用场景,并讨论了如何通过引入抽象外观类来提高系统的可扩展性。
【九】设计模式~~~结构型模式~~~外观模式(Java)
|
4月前
|
设计模式 Java
【八】设计模式~~~结构型模式~~~装饰模式(Java)
文章详细介绍了装饰模式(Decorator Pattern),这是一种对象结构型模式,用于在不使用继承的情况下动态地给对象添加额外的职责。装饰模式通过关联机制,使用装饰器类来包装原有对象,并在运行时通过组合的方式扩展对象的行为。文章通过图形界面构件库的设计案例,展示了装饰模式的动机、定义、结构、优点、缺点以及适用场景,并提供了Java代码实现和应用示例。装饰模式提高了系统的灵活性和可扩展性,适用于需要动态、透明地扩展对象功能的情况。
【八】设计模式~~~结构型模式~~~装饰模式(Java)
|
4月前
|
设计模式 XML 存储
【七】设计模式~~~结构型模式~~~桥接模式(Java)
文章详细介绍了桥接模式(Bridge Pattern),这是一种对象结构型模式,用于将抽象部分与实现部分分离,使它们可以独立地变化。通过实际的软件开发案例,如跨平台视频播放器的设计,文章阐述了桥接模式的动机、定义、结构、优点、缺点以及适用场景,并提供了完整的代码实现和测试结果。桥接模式适用于存在两个独立变化维度的系统,可以提高系统的可扩展性和灵活性。
【七】设计模式~~~结构型模式~~~桥接模式(Java)
|
4月前
|
设计模式 XML 存储
【六】设计模式~~~结构型模式~~~适配器模式(Java)
文章详细介绍了适配器模式(Adapter Pattern),这是一种结构型设计模式,用于将一个类的接口转换成客户期望的另一个接口,使原本不兼容的接口能够一起工作,提高了类的复用性和系统的灵活性。通过对象适配器和类适配器两种实现方式,展示了适配器模式的代码应用,并讨论了其优点、缺点以及适用场景。
|
4月前
|
设计模式 缓存 Java
【十一】设计模式~~~结构型模式~~~代理模式(Java)
文章详细介绍了代理模式(Proxy Pattern),这是一种对象结构型模式,用于给对象提供一个代理以控制对它的访问。文中阐述了代理模式的动机、定义、结构、优点、缺点和适用环境,并探讨了远程代理、虚拟代理、保护代理等不同代理形式。通过一个商务信息查询系统的实例,展示了如何使用代理模式来增加身份验证和日志记录功能,同时保持客户端代码的无差别对待。此外,还讨论了代理模式在分布式技术和Spring AOP中的应用,以及动态代理的概念。
【十一】设计模式~~~结构型模式~~~代理模式(Java)
|
5月前
|
设计模式 存储 JavaScript
js设计模式【详解】—— 享元模式
js设计模式【详解】—— 享元模式
67 6
|
6月前
|
设计模式 缓存 Java
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
|
6月前
|
设计模式 存储 Java
Java设计模式之享元模式详解
Java设计模式之享元模式详解