一、享元模式是什么?
享元模式,主要在于共享通用对象,减少内存的使用,提升系统的访问效率。而这部分共享对象通常比较耗费内存或者需要查询大量接口或者使用数据库资源,因此统一抽离作为共享对象使用。
另外享元模式可以分为在服务端和客户端,一般互联网H5和Web场景下大部分数据都需要服务端进行处理,比如数据库连接池的使用、多线程线程池的使用,除了这些功能外,还有些需要服务端进行包装后的处理下发给客户端,因为服务端需要做享元处理。但在一些游戏场景下,很多都是客户端需要进行渲染地图效果,比如;树木、花草、鱼虫,通过设置不同元素描述使用享元公用对象,减少内存的占用,让客户端的游戏更加流畅。
在享元模型的实现中需要使用到享元工厂来进行管理这部分独立的对象和共享的对象,避免出现线程安全的问题。
二、类图结构
以上是我们模拟查询活动场景的类图结构,左侧构建的是享元工厂,提供固定活动数据的查询,右侧是Redis存放的库存数据。
三、代码示例
活动基础信息类:
public class Activity {
private Long id; // 活动ID
private String name; // 活动名称
private String desc; // 活动描述
private Date startTime; // 开始时间
private Date stopTime; // 结束时间
private Stock stock; // 活动库存
// ...get/set
}
活动库存信息类:
public class Stock {
private int total; // 库存总量
private int used; // 库存已用
// ...get/set
}
享元工厂类:
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;
}
}
这里提供的是一个享元工厂,通过map结构来存放从数据库表或者rpc接口中查询到的数据,用于下次可以直接获取,当然也有些时候为了分布式的获取,会把数据存放到redis中,可以按需选择
模拟redis的类:
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();
}
}
这里是通过一个定时任务来模拟库存的使用,redis的操作工具类
活动的控制类:
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;
}
}
在活动控制类中使用享元工厂获取一般不怎么变化的活动信息,然后在从redis中获取库存的变化信息,最终通过统一的控制类就可以把完整包装后的活动信息进行返回给调用方
测试类:
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
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);
logger.info("测试结果:{} {}", req, JSON.toJSONString(activity));
Thread.sleep(1200);
}
}
}
总结
关于享元模式,需要学习的点就是对于享元工厂的设计,在一些有大量重复对象可以复用的场景下,可以使用此场景在服务端减少接口的调用,在客户端减少内存的使用,是这个设计模式主要的应用方式。
另外通过map结构的使用方式也可以看到,使用一个固定id来存放和获取对象,是非常关键的点。而且不只是在享元模式中使用,一些其他工厂模式、适配器模式、组合模式中都可以通过map结构存放服务供外部获取,减少if else的判断使用。
另外就是要注意合理拆分变化与不变化的数据界限。
注:本文是参考和学习小傅哥的重学 Java 设计模式的记录笔记,强烈推荐。