设计模式 - 结构型模式_享元模式

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: 享元⼯⼚的设计,在⼀些有⼤量᯿复对象可复⽤的场景下,使⽤此场景在服务端减少接⼝的调⽤,在客户端减少内存的占⽤。是这个设计模式的主要应⽤⽅式。

@[toc]

在这里插入图片描述


结构型模式

结构型模式主要是解决如何将对象和类组装成较大的结构, 并同时保持结构的灵活和⾼效。

结构型模式包括:适配器、桥接、组合、装饰器、外观、享元、代理,这7类

在这里插入图片描述


概述

在这里插入图片描述

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

另外享元模式可以分为在服务端和客户端.

  • ⼀般互联⽹H5和Web场景下⼤部分数据都需要服务端进⾏处理,⽐如数据库连接池的使⽤、多线程线程池的使⽤,除了这些功能外,还有些需要服务端进⾏包装后的处理下发给客户端,因为服务端需要做享元处理。
  • 但在⼀些游戏场景下,很多都是客户端需要进⾏渲染地图效果,⽐如;树⽊、花草、⻥⾍,通过设置不同元素描述使⽤享元公⽤对象,减少内存的占⽤,让客户端的游戏更加流畅。

在享元模型的实现中需要使⽤到享元⼯⼚来进⾏管理这部分独⽴的对象和共享的对象,避免出现线程安全的问题。


Case

在这里插入图片描述
模拟在商品秒杀场景下使⽤享元模式查询优化.

随着业务的快速发展秒杀的⽤户越来越多,这个时候数据库已经扛不住了,⼀般都会使⽤redis的分布式锁来控制商品库存。

同时在查询的时候也不需要每⼀次对不同的活动查询都从库中获取,因为这⾥除了库存以外其他的活动商品信息都是固定不变的,以此这⾥⼀般⼤家会缓存到内存中。

这⾥我们模拟使⽤享元模式⼯⼚结构,提供活动商品的查询。活动商品相当于不变的信息,⽽库存部分属于变化的信息


Bad Impl

逻辑很简单,⼀⽚⽚的固定内容和变化内容的查询组合,CV的哪⾥都是!

这部分逻辑的查询在⼀般情况都是先查询固定信息,在使⽤过滤的或者添加if判断的⽅式补充变化的信息,也就是库存。这样写最开始并不会看出来有什么问题,但随着⽅法逻辑的增加,后⾯就越来越多重复的代码

在这里插入图片描述

⼯程结构⽐较简单,之后⼀个控制类⽤于查询活动信息。

public class ActivityController {

    public Activity queryActivityInfo(Long id) {
        // 模拟从实际业务应用从接口中获取活动信息
        Activity activity = new Activity();
        activity.setId(10001L);
        activity.setName("图书嗨乐");
        activity.setDesc("图书优惠券分享激励分享活动第二期");
        activity.setStartTime(new Date());
        activity.setStopTime(new Date());
        activity.setStock(new Stock(1000,1));
        return activity;
    }

}
  • 这⾥模拟的是从接⼝中查询活动信息,基本也就是从数据库中获取所有的商品信息和库存。有点像最开始写的系统,数据库就可以抗住购物量。
  • 当后续因为业务的发展需要扩展代码将库存部分交给redis处理,那么就需要从redis中获取活动的库存,⽽不是从库中,否则将造成数据不统⼀的问题

Better Impl

接下来使⽤享元模式来进⾏代码优化

享元模式⼀般情况下使⽤此结构在平时的开发中并不太多,除了⼀些线程池、数据库连接池外,再就是游戏场景下的场景渲染。另外这个设计的模式思想是减少内存的使⽤提升效率,与我们之前使⽤的原型模式通过克隆对象的⽅式⽣成复杂对象,减少rpc的调⽤,都是此类思想.

【⼯程结构】

在这里插入图片描述

【享元模式模型结构】

在这里插入图片描述

  • 模拟查询活动场景的类图结构,左侧构建的是享元⼯⼚,提供固定活动数据的查询,右侧是Redis存放的库存数据。
  • 最终交给活动控制类来处理查询操作,并提供活动的所有信息和库存。因为库存是变化的,所以我们模拟的 RedisUtils 中设置了定时任务使⽤库存。

【活动信息】

public class Activity {

    private Long id;        // 活动ID
    private String name;    // 活动名称
    private String desc;    // 活动描述
    private Date startTime; // 开始时间
    private Date stopTime;  // 结束时间
    private Stock stock;    // 活动库存

    // set get 
}

这⾥的对象类⽐较简单,只是⼀个活动的基础信息: id、名称、描述、时间和库存。


【库存信息】

public class Stock {

    private int total; // 库存总量
    private int used;  // 库存已用

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

    // set get 
}

这⾥是库存数据我们单独提供了⼀个类进⾏保存数据。


【享元⼯⼚】

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 结构存放已经从库表或者接⼝中查询到的数据,存放到内存中,⽤于下次可以直接获取。
  • 这样的结构⼀般在开发中还是⽐较常⻅的,当然也有些时候为了分布式的获取,会把数据存放到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;
    }

}
  • 在活动控制类中使⽤了享元⼯⼚获取活动信息,查询后将库存信息在补充上。因为库存信息是变化的,⽽活动信息是固定不变的
  • 最终通过统⼀的控制类就可以把完整包装后的活动信息返回给调⽤⽅

【测试验证】

    @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);
        }
    }

在这里插入图片描述

通过活动查询控制类,在 for 循环的操作下查询了⼗次活动信息,同时为了保证库存定时任务的变化,加了睡眠操作,实际的开发中不会有这样的睡眠。

仔细看下 stock 部分的库存是⼀直在变化的,其他部分是活动信息,是固定的,所以我们使⽤享元模式来将这样的结构进⾏拆分。


小结

享元⼯⼚的设计,在⼀些有⼤量᯿复对象可复⽤的场景下,使⽤此场景在服务端减少接⼝的调⽤,在客户端减少内存的占⽤。是这个设计模式的主要应⽤⽅式。

另外通过 map 结构的使⽤⽅式也可以看到,使⽤⼀个固定id来存放和获取对象,是⾮常关键的点。⽽且不只是在享元模式中使⽤,⼀些其他⼯⼚模式、适配器模式、组合模式中都可以通过map结构存放服务供外部获取,减少ifelse的判断使⽤。

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

在这里插入图片描述

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2天前
|
设计模式 存储 Java
Java设计模式之享元模式详解
Java设计模式之享元模式详解
|
1天前
|
设计模式 缓存 Java
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
|
1天前
|
设计模式 存储 SQL
设计模式——结构型模式(适配器,桥接,过滤器,组合,装饰器,外观,享元,代理)(2)
设计模式——结构型模式(适配器,桥接,过滤器,组合,装饰器,外观,享元,代理)
|
1天前
|
设计模式 存储 前端开发
设计模式——结构型模式(适配器,桥接,过滤器,组合,装饰器,外观,享元,代理)(1)
设计模式——结构型模式(适配器,桥接,过滤器,组合,装饰器,外观,享元,代理)
|
1天前
|
设计模式 开发框架 Java
简单理解设计模式——享元模式-线程池-任务(task)
简单理解设计模式——享元模式-线程池-任务(task)
|
3天前
|
设计模式
享元模式-大话设计模式
享元模式-大话设计模式
4 0
|
8天前
|
设计模式
设计模式之结构型模式
设计模式之结构型模式
|
1月前
|
设计模式 缓存 监控
JAVA设计模式之结构型模式
结构模型:适配器模型、桥接模型、过滤器模型、组合模型、装饰器模型、外观模型、享受元模型和代理模型。
32 3
|
1月前
|
设计模式 Go
[设计模式 Go实现] 结构型~享元模式
[设计模式 Go实现] 结构型~享元模式
|
1月前
|
设计模式 Java 开发者
【搞懂设计模式】享元模式:共享节约,皆大欢喜!
【搞懂设计模式】享元模式:共享节约,皆大欢喜!
31 0

热门文章

最新文章