>享元模式
# 定义
享元模式,主要在于共享通用对象,减少内存的使用,提升系统的访问效率。而这部分共享对象通常比较耗费内存或者需要查询大量接口或者使用数据库资源,因此统一抽离作为共享对象使用。
另外享元模式可以分为在服务端和客户端,一般互联网H5和Web场景下大部分数据都需要服务端进行处理,比如数据库连接池的使用、多线程线程池的使用,除了这些功能外,还有些需要服务端进行包装后的处理下发给客户端,因为服务端需要做享元处理。但在一些游戏场景下,很多都是客户端需要进行渲染地图效果,比如;树木、花草、鱼虫,通过设置不同元素描述使用享元公用对象,减少内存的占用,让客户端的游戏更加流畅。
在享元模型的实现中需要使用到享元工厂来进行管理这部分独立的对象和共享的对象,避免出现线程安全的问题。
优点:大大减少对象的创建,降低系统的内存,使效率提高。
缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
使用场景:
1. 系统有大量相似对象。
2. 需要缓冲池的场景。
# 实践
模拟在商品秒杀场景下使用享元模式查询优化。
活动对象
```java
@Data
public class Activity {
// 活动ID
private Long id;
// 活动名称
private String name;
// 活动描述
private String desc;
// 开始时间
private Date startTime;
// 结束时间
private Date stopTime;
// 活动库存
private Stock stock;
}
```
库存信息
```java
@Data
public class Stock {
// 库存总量
private int total;
// 库存已用
private int used;
public Stock(int total, int used) {
this.total = total;
this.used = used;
}
}
```
创建一个享元工厂
```java
public class ActivityFactory {
static Map<Long, Activity> activityMap = new HashMap<Long, Activity>();
public static Activity getActivity(Long id) {
Activity activity = activityMap.get(id);
if (null == activity) {
// 模拟从实际业务应用从接口中获取活动信息
activity = new Activity();
activity.setId(10001L);
activity.setName("图书嗨乐");
activity.setDesc("图书优惠券分享激励分享活动第二期");
activity.setStartTime(new Date());
activity.setStopTime(new Date());
activityMap.put(id, activity);
}
return activity;
}
}
```
Redis工具类,模拟减库存
```java
public class RedisUtils {
private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
private AtomicInteger stock = new AtomicInteger(0);
public RedisUtils() {
scheduledExecutorService.scheduleAtFixedRate(() -> {
// 模拟库存消耗
stock.addAndGet(1);
}, 0, 100000, TimeUnit.MICROSECONDS);
}
public int getStockUsed() {
return stock.get();
}
}
```
活动控制类
```java
public class ActivityController {
private RedisUtils redisUtils = new RedisUtils();
public Activity queryActivityInfo(Long id) {
Activity activity = ActivityFactory.getActivity(id);
// 模拟从Redis中获取库存变化信息
Stock stock = new Stock(1000, redisUtils.getStockUsed());
activity.setStock(stock);
return activity;
}
}
```
测试类
```java
@Slf4j
public class ApiTest {
private ActivityController activityController = new ActivityController();
@Test
public void test_queryActivityInfo() throws InterruptedException {
for (int idx = 0; idx < 10; idx++) {
Long req = 10001L;
Activity activity = activityController.queryActivityInfo(req);
log.info("测试结果:{} {}", req, JSON.toJSONString(activity));
Thread.sleep(1200);
}
}
}
```
测试结果:
```
15:39:28.404 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":1},"stopTime":1644824368339}
15:39:29.620 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":13},"stopTime":1644824368339}
15:39:30.829 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":25},"stopTime":1644824368339}
15:39:32.042 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":38},"stopTime":1644824368339}
15:39:33.252 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":50},"stopTime":1644824368339}
15:39:34.460 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":62},"stopTime":1644824368339}
15:39:35.668 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":74},"stopTime":1644824368339}
15:39:36.879 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":86},"stopTime":1644824368339}
15:39:38.088 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":98},"stopTime":1644824368339}
15:39:39.294 [main] INFO ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1644824368339,"stock":{"total":1000,"used":110},"stopTime":1644824368339}
```
# 总结
关于享元模式的设计可以着重学习享元工厂的设计,在一些有大量重复对象可复用的场景下,使用此场景在服务端减少接口的调用,在客户端减少内存的占用。是这个设计模式的主要应用方式。
另外通过map结构的使用方式也可以看到,使用一个固定id来存放和获取对象,是非常关键的点。而且不只是在享元模式中使用,一些其他工厂模式、适配器模式、组合模式中都可以通过map结构存放服务供外部获取,减少ifelse的判断使用。
当然除了这种设计的减少内存的使用优点外,也有它带来的缺点,在一些复杂的业务处理场景,很不容易区分出内部和外部状态,就像我们活动信息部分与库存变化部分。如果不能很好的拆分,就会把享元工厂设计的非常混乱,难以维护。

【设计模式】享元模式