前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【设计模式】享元模式

【设计模式】享元模式

作者头像
Li_XiaoJin
发布2022-06-10 18:34:06
3080
发布2022-06-10 18:34:06
举报
文章被收录于专栏:Lixj's BlogLixj's Blog

享元模式

定义

享元模式,主要在于共享通用对象,减少内存的使用,提升系统的访问效率。而这部分共享对象通常比较耗费内存或者需要查询大量接口或者使用数据库资源,因此统一抽离作为共享对象使用。

另外享元模式可以分为在服务端和客户端,一般互联网H5和Web场景下大部分数据都需要服务端进行处理,比如数据库连接池的使用、多线程线程池的使用,除了这些功能外,还有些需要服务端进行包装后的处理下发给客户端,因为服务端需要做享元处理。但在一些游戏场景下,很多都是客户端需要进行渲染地图效果,比如;树木、花草、鱼虫,通过设置不同元素描述使用享元公用对象,减少内存的占用,让客户端的游戏更加流畅。

在享元模型的实现中需要使用到享元工厂来进行管理这部分独立的对象和共享的对象,避免出现线程安全的问题。

优点:大大减少对象的创建,降低系统的内存,使效率提高。

缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

使用场景:

  1. 系统有大量相似对象。
  2. 需要缓冲池的场景。

实践

模拟在商品秒杀场景下使用享元模式查询优化。

活动对象

代码语言:javascript
复制
@Data
public class Activity {
    
    // 活动ID
    private Long id;
    // 活动名称
    private String name;
    // 活动描述
    private String desc;
    // 开始时间
    private Date startTime;
    // 结束时间
    private Date stopTime;
    // 活动库存
    private Stock stock;

}

库存信息

代码语言:javascript
复制
@Data
public class Stock {

    // 库存总量
    private int total;

    // 库存已用
    private int used;

    public Stock(int total, int used) {
        this.total = total;
        this.used = used;
    }

}

创建一个享元工厂

代码语言:javascript
复制
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工具类,模拟减库存

代码语言:javascript
复制
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();
    }
}

活动控制类

代码语言:javascript
复制
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;

    }
}

测试类

代码语言:javascript
复制
@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);
        }
    }
}

测试结果:

代码语言:javascript
复制
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的判断使用。

当然除了这种设计的减少内存的使用优点外,也有它带来的缺点,在一些复杂的业务处理场景,很不容易区分出内部和外部状态,就像我们活动信息部分与库存变化部分。如果不能很好的拆分,就会把享元工厂设计的非常混乱,难以维护。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可 Links: https://lixj.fun/archives/设计模式-享元模式

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-01-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义
  • 实践
  • 总结
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档