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

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

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 方法,并传递了不同的外部状态。

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

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

 

相关文章
|
22天前
|
设计模式 存储 Java
23种设计模式,享元模式的概念优缺点以及JAVA代码举例
【4月更文挑战第6天】享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享技术有效地支持大量细粒度对象的重用。这个模式在处理大量对象时非常有用,特别是当这些对象中的许多实例实际上可以共享相同的状态时,从而可以减少内存占用,提高程序效率
35 4
|
4月前
|
设计模式
二十三种设计模式全面解析-组合模式与享元模式的结合应用:实现对象的共享和高效管理
二十三种设计模式全面解析-组合模式与享元模式的结合应用:实现对象的共享和高效管理
|
1月前
|
设计模式 存储 安全
Java设计模式---结构型模式
Java设计模式---结构型模式
|
3月前
|
设计模式 Java 应用服务中间件
设计模式 -结构型模式_门面模式(外观模式) Facade Pattern 在开源软件中的应用
设计模式 -结构型模式_门面模式(外观模式) Facade Pattern 在开源软件中的应用
31 1
|
13天前
|
设计模式 存储 Java
小谈设计模式(27)—享元模式
小谈设计模式(27)—享元模式
|
1月前
|
设计模式 缓存 Java
设计模式之享元模式
设计模式之享元模式
|
1月前
|
设计模式 存储 缓存
【设计模式】享元模式
【设计模式】享元模式
|
3月前
|
设计模式 存储 缓存
聊聊Java设计模式-享元模式
享元(Flyweight)模式:顾名思义就是**被共享的单元**。意图是复用对象,节省内存,提升系统的访问效率。比如在红白机冒险岛游戏中的背景花、草、树木等对象,实际上是可以多次被不同场景所复用共享,也是为什么以前的游戏占用那么小的内存,却让我们感觉地图很大的原因。
17 3
聊聊Java设计模式-享元模式
|
3月前
|
设计模式 存储 Go
Golang设计模式——16享元模式
Golang设计模式——16享元模式
23 0
|
3月前
|
设计模式 缓存 前端开发
【设计模式】之享元模式
享元模式是一种优化大量相似对象创建和管理的设计模式,在前端开发中可以用于优化图片懒加载、表单验证等场景。它通过共享相似对象来减少内存使用和提高性能,同时也简化了代码逻辑。然而,它也增加了系统的复杂性,并且需要注意共享状态的安全性。在实际应用中,我们需要根据具体场景权衡使用享元模式带来的优缺点。
52 1