1、简介
享元模式(Flyweight Pattern)是一种结构型设计模式,用于优化大量相似对象的内存使用。
它通过共享对象来减少内存使用,从而提高程序的性能和效率。享元模式的核心思想是共享对象,也就是将相同或相似的对象共享一份内存空间。
2、🔺对象类型
在享元模式中,通常有两种对象类型:内部状态和外部状态。
内部状态是可以共享的,而外部状态是不可以共享的。
内部状态是指在对象创建时确定并且不会改变的状态,外部状态是指在对象创建之后可能会改变的状态。
通过将相同的内部状态的对象缓存起来,减少了相同内部状态的对象的数量,从而达到减少内存的目的。当需要使用相同内部状态的对象时,只需要从缓存中取出即可。
享元模式的实现需要分离出对象的内部状态和外部状态,然后使用工厂模式来创建和管理对象。
此处为语雀内容卡片,点击链接查看:1、工厂方法 · 语雀
内部状态可以作为享元对象的属性,而外部状态则需要作为方法的参数传入。当需要创建对象时,先检查对象池中是否已经有对应内部状态的对象,如果有则直接返回,如果没有则创建新对象并将其添加到对象池中。在使用对象时,只需要传入外部状态,即可得到完整的对象。
2.1、享元之工厂模式
享元模式使用的是工厂方法模式,而不是抽象工厂模式。
工厂方法模式是一种创建型设计模式,用于定义一个创建对象的接口,但将具体的对象创建延迟到子类中实现。在享元模式中,我们使用了享元工厂类来管理共享对象的创建和缓存,这个工厂类就是工厂方法模式中的工厂。
具体地说,在享元模式中,我们定义了一个 Flyweight 接口,用于定义共享对象的接口;一个具体的享元类 ConcreteFlyweight,用于实现共享对象的具体操作;以及一个享元工厂类 FlyweightFactory,用于管理共享对象的创建和缓存。在享元工厂类中,我们使用工厂方法 getFlyweight 来创建共享对象。
相比之下,抽象工厂模式是一种创建型设计模式,用于定义一系列相关对象的接口,而不需要指定具体的类。在抽象工厂模式中,我们定义了一个抽象工厂类和一组抽象产品类,具体的工厂和产品类是通过实现这些抽象类来实现的。
因此,尽管享元模式和抽象工厂模式都涉及到对象的创建,但它们的目的和实现方式是不同的,因此享元模式使用的是工厂方法模式,而不是抽象工厂模式。
3、组成部分
享元模式包含以下组成部分:
- Flyweight(享元):是抽象享元类或者具体享元类的接口,通过这个接口可以接收外部状态并进行相应的处理。
- ConcreteFlyweight(具体享元类):是实现 Flyweight 接口的具体类,其中存储了享元的内部状态。
- UnsharedConcreteFlyweight(非共享具体享元类):是一些不需要共享的具体享元类,一般情况下很少使用。
- FlyweightFactory(享元工厂类):是负责创建和管理享元对象的工厂类,其中维护了一个享元池用于存储已经创建的享元对象,避免重复创建。
- Client(客户端):是调用享元对象的客户端代码,可以通过享元工厂获取已经创建的享元对象,并将外部状态传递给享元对象进行处理。
这些组成部分共同协作,实现了对象的共享和复用,从而降低了系统的内存消耗和运行时开销。
4、使用场景
享元模式是一种优化内存使用的设计模式,适用于以下场景:
- 对象数量大且相似:如果一个系统中有大量相似的对象需要被创建,而这些对象的内部状态可以共享,那么可以使用享元模式来减少内存的使用,提高程序的性能和效率。比如说,一个文本编辑器中需要创建大量的字符对象,这些字符对象有很多相同的属性,比如字体和颜色,可以使用享元模式来实现。
- 内部状态较少:如果一个对象的内部状态较少,那么使用享元模式可以有效地减少内存的使用。比如说,一个围棋游戏中的棋子,它们的内部状态只有颜色和形状,可以使用享元模式来减少内存的使用。
- 大对象:如果一个对象很大,而系统中需要创建大量的这种对象,那么可以使用享元模式来减少内存的使用。比如说,一个游戏中需要创建大量的怪物对象,而这些对象都包含了很多属性,可以使用享元模式来优化内存使用。
- 对象的状态可以外部化:如果一个对象的状态可以外部化,那么可以使用享元模式来减少内存的使用。比如说,一个网络聊天室中的用户对象,它们的状态可以外部化,比如说用户名、密码、头像等,可以使用享元模式来优化内存使用。
- 低延迟要求:如果一个系统需要实现低延迟的操作,那么可以使用享元模式来优化内存使用。由于享元对象已经被缓存起来了,因此可以快速地进行对象的创建和访问,从而实现低延迟的操作。
总之,如果一个系统中有大量相似的对象需要被创建,并且这些对象的内部状态可以共享,那么就可以使用享元模式来优化内存使用,提高程序的性能和效率。
5、优缺点
享元模式是一种优化内存使用的设计模式,它具有以下优点和缺点:
优点:
- 减少内存使用:使用享元模式可以减少系统中对象的数量,从而减少内存的使用,提高系统的性能和效率。
- 提高性能:由于享元对象已经被缓存起来了,因此可以快速地进行对象的创建和访问,从而提高系统的性能和效率。
- 提高可维护性:使用享元模式可以将对象的状态分为内部状态和外部状态,从而提高代码的可维护性。
- 可以实现对象池:使用享元模式可以实现对象池,从而实现对象的复用,提高系统的性能和效率。
缺点:
- 增加复杂度:使用享元模式需要将对象的状态分为内部状态和外部状态,这增加了代码的复杂度。
- 限制状态共享:使用享元模式需要将对象的状态分为内部状态和外部状态,如果对象的状态不能分为内部状态和外部状态,那么就不能使用享元模式。
- 破坏封装性:使用享元模式需要将对象的状态分为内部状态和外部状态,这可能会破坏对象的封装性。
总之,享元模式是一种优化内存使用的设计模式,它可以减少内存的使用,提高系统的性能和效率,但也有一些缺点,比如增加代码的复杂度、限制状态共享和破坏对象的封装性。在使用享元模式时,需要根据具体的情况权衡优缺点,选择合适的方案。
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 方法,并传递了不同的外部状态。
当我们运行这段代码时,可以看到如下输出结果:
从输出结果可以看出,享元模式可以有效地共享对象,减少内存的使用,提高系统的性能和效率。