【设计模式-结构型】享元模式
一、什么是享元模式
享元模式确实是一种结构型设计模式,其名称“享元”虽然听起来有些抽象,但其实包含了该模式的核心概念。我们可以将“享元”这个名字拆开来理解,以便更好地把握其含义和用途。
享元模式的命名解释
-
“享”:共享:享元模式的核心在于共享已存在的对象,而不是每次都创建新的对象。通过这种方式,多个客户端可以共享同一个对象实例,而不是每个客户端都拥有自己的独立实例。这种共享机制显著减少了内存的使用,提高了系统的性能和资源利用率。
-
“元”:基本元素或单元:享元模式将对象分解为基本的、可共享的单元。这些单元(享元对象)是构成更复杂对象的“元”素。通过共享这些基本单元,可以在多个地方复用它们,从而减少内存中对象的数量。
二、为什么用享元模式
- 减少内存消耗:享元模式通过共享已存在的对象,减少了内存中对象的数量。这对于需要创建大量相似对象的场景特别有用,可以显著降低内存占用,提高系统的资源利用率。
- 管理大量相似对象:当系统中存在大量相似或相同的对象时,享元模式可以有效地管理这些对象,避免重复创建。
- 简化对象管理:享元工厂提供了一个集中的点来管理对象的创建和复用,使得对象的管理更加简单和高效。客户端代码不需要关心对象的创建细节,只需要通过工厂获取对象即可。
- 优化资源使用:享元模式通过共享对象,优化了资源的使用。这对于资源受限的系统(如嵌入式系统或移动端应用)特别有用,可以确保系统在有限的资源下高效运行。
三、享元模式示例
3.1 用一个生活场景表述享元模式
是不是很抽象,什么内部状态,什么共享,什么不能共享,听的有点晕乎乎。他和缓存或者内存中开发常用的map存储有啥区别,不都一样吗?这个差别可以留到下面解答。这个环节主要通过举一个例子来说明享元对应生活中的例子,并解释清楚享元的角色。
享元我的理解,其实和近几年一个商业模式 特别的像。 就是我们常说的 “共享模式“,我相信多数人都接触过,比如 共享单车,共享汽车,共享充电宝等等。没错,他们就是用了享元模式。借阅人其实就是客户端,他会拿app去骑车,你会告诉公司 我需要一辆车,公司为了省钱肯定会告诉你附近哪里有一辆电动车,那么这个电动车就是内部状态,也就是可以共享的。也就是享元对象,那么谁借,谁骑行,骑到哪里,还车。 车的定位,借阅历史信息就是 外部状态。 当借的人多了一看,咋回事附近没车啊,这个时候 享元工厂 也就是公司 ,就会紧急生产享元对象(单车)进行投放。那么我们下面对应到享元对象的角色。
3.2 享元模式角色定义
1. 抽象享元(Flyweight):定义了享元对象的接口,通过这个接口享元可以接受并作用于外部状态。在共享单车的例子中,这可以看作是共享单车的基本接口,定义了共享单车的基本操作,如解锁、骑行、归还等。
// 享元接口:共享单车
interface SharedBike {
void use(String userId, String location, LocalDate borrowDate, LocalDate returnDate);
}
2. 具体享元(ConcreteFlyweight):实现抽象享元接口,为内部状态提供存储空间。具体享元对象必须是可共享的,它所存储的状态必须是内部状态,即独立于具体享元对象的场景。在共享单车的例子中,具体享元对象可以看作是每一辆具体的共享单车,其内部状态包括车型、颜色、配置等,这些属性在共享单车的生命周期内不会改变,并且可以被多个用户共享。
// 具体享元类:具体共享单车
class ConcreteSharedBike implements SharedBike {
private String model;
private String color;
private String configuration;
public ConcreteSharedBike(String model, String color, String configuration) {
this.model = model;
this.color = color;
this.configuration = configuration;
}
@Override
public void use(String userId, String location, LocalDate borrowDate, LocalDate returnDate) {
System.out.println("车型: " + model);
System.out.println("颜色: " + color);
System.out.println("配置: " + configuration);
System.out.println("借阅者: " + userId);
System.out.println("位置: " + location);
System.out.println("借阅日期: " + borrowDate);
System.out.println("归还日期: " + returnDate);
}
}
3. 享元工厂(FlyweightFactory):创建并管理享元对象,确保享元对象可以被正确地共享和复用。享元工厂维护一个对象池,存储已经创建的享元对象,以便复用。在共享单车的例子中,享元工厂可以看作是共享单车的运营公司,负责管理和调度共享单车,确保用户可以方便地获取和归还单车。
// 享元工厂:共享单车工厂
class SharedBikeFactory {
private Map<String, SharedBike> bikes = new HashMap<>();
public SharedBike getBike(String model, String color, String configuration) {
String key = model + color + configuration;
if (!bikes.containsKey(key)) {
bikes.put(key, new ConcreteSharedBike(model, color, configuration));
}
return bikes.get(key);
}
}
4.客户端(Client):使用享元对象的代码,通过享元工厂来请求所需的享元对象。客户端不直接创建享元对象,而是通过享元工厂来获取。在共享单车的例子中,客户端可以看作是用户,用户通过手机应用请求一辆共享单车,而不是自己去创建一辆新的单车。
// 客户端代码
public class FlyweightPatternDemo {
public static void main(String[] args) {
SharedBikeFactory factory = new SharedBikeFactory();
// 用户1借阅一辆共享单车
SharedBike bike1 = factory.getBike("共享单车A", "蓝色", "标准配置");
bike1.use("用户1", "停车场A", LocalDate.now(), LocalDate.now().plusDays(1));
// 用户2借阅一辆共享单车
SharedBike bike2 = factory.getBike("共享单车A", "蓝色", "标准配置");
bike2.use("用户2", "停车场B", LocalDate.now(), LocalDate.now().plusDays(1));
// 用户3借阅一辆不同配置的共享单车
SharedBike bike3 = factory.getBike("共享单车B", "黄色", "高级配置");
bike3.use("用户3", "停车场C", LocalDate.now(), LocalDate.now().plusDays(1));
// 检查是否共享
System.out.println("bike1 和 bike2 是否相同: " + (bike1 == bike2));
}
}
车型: 共享单车A
颜色: 蓝色
配置: 标准配置
借阅者: 用户1
位置: 停车场A
借阅日期: 2024-01-15
归还日期: 2024-01-16
车型: 共享单车A
颜色: 蓝色
配置: 标准配置
借阅者: 用户2
位置: 停车场B
借阅日期: 2024-01-15
归还日期: 2024-01-16
车型: 共享单车B
颜色: 黄色
配置: 高级配置
借阅者: 用户3
位置: 停车场C
借阅日期: 2024-01-15
归还日期: 2024-01-16
bike1 和 bike2 是否相同: true
内部状态和外部状态
-
内部状态(Intrinsic State):指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。在共享单车的例子中,内部状态包括车型、颜色、配置等,这些属性在共享单车的生命周期内不会改变,并且可以被多个用户共享。
-
外部状态(Extrinsic State):指对象得以依赖的一个标记,是随环境改变而改变的,不可共享的状态。在共享单车的例子中,外部状态包括用户的借阅记录、当前位置、借阅时间、归还时间等,这些属性是特定于每次借阅的,每次借阅时都会有所不同。
3.3 享元模式和开发中类似的Map有啥区别
其实炸一看是不是很像map,不就是缓存吗,你给我搞啥享元。其实他还是有区别的。
-
目的不同:
-
享元模式:
-
目的:通过共享已存在的对象来减少内存的使用,提高系统的性能和资源利用率。
-
应用场景:适用于需要创建大量相似对象的场景,通过共享对象来减少内存占用。
-
-
缓存(Map):
-
目的:提高数据访问速度,减少对数据库或文件系统的访问次数,避免数据击穿。
-
应用场景:适用于需要频繁访问、计算成本高昂的数据,通过缓存来提高系统的性能。例如,数据库查询结果缓存、API响应缓存等。
-
-
-
状态管理不同:
-
享元模式:
-
内部状态(Intrinsic State):对象的固有属性,这些属性在对象的生命周期内不会改变,并且可以被多个客户端共享。
-
外部状态(Extrinsic State):对象的上下文相关属性,这些属性在不同的使用场景中可能会改变,并且不能被共享。
-
-
缓存(Map):
-
内部状态:缓存中存储的数据通常是不变的,只包含对象的内部状态。例如,缓存中的数据库查询结果、配置信息等。
-
外部状态:缓存本身不涉及外部状态的管理,外部状态通常由客户端或其他组件管理。
-
-
-
实现方式不同:
-
享元模式:
-
享元工厂(FlyweightFactory):负责管理和共享享元对象。当客户端需要一个对象时,工厂首先检查是否已经存在具有相同内部状态的对象。如果存在,则返回现有的对象;如果不存在,则创建一个新的对象并将其添加到对象池中。
-
客户端:通过享元工厂获取享元对象,并传递外部状态给享元对象,以便在具体的操作中使用。
-
-
缓存(Map):
-
缓存管理:通常由一个简单的Map实现,键(Key)是缓存的标识,值(Value)是缓存的数据。缓存管理器负责存储和检索缓存数据。
-
客户端:直接通过键从缓存中获取数据,如果缓存中没有数据,则从数据库或其他数据源获取并存储到缓存中。
-
-