2024050302-重学 Java 设计模式《实战享元模式》

news2024/11/18 23:33:32

重学 Java 设计模式:实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」

一、前言

程序员👨‍💻‍的上下文是什么?

很多时候一大部分编程开发的人员都只是关注于功能的实现,只要自己把这部分需求写完就可以了,有点像被动的交作业。这样的问题一方面是由于很多新人还不了解程序员的职业发展,还有一部分是对于编程开发只是工作并非兴趣。但在程序员的发展来看,如果不能很好的处理上文(产品),下文(测试),在这样不能很好的了解业务和产品发展,也不能编写出很有体系结构的代码,日久天长,1到3年、3到5年,就很难跨越一个个技术成长的分水岭。

拥有接受和学习新知识的能力

你是否有感受过小时候在什么都还不会的时候接受知识的能力很强,但随着我们开始长大后,慢慢学习能力、处事方式、性格品行,往往会固定。一方面是形成了各自的性格特征,一方面是圈子已经固定。但也正因为这样的故步,而很少愿意听取别人的意见,就像即使看到了一整片内容,在视觉盲区下也会过掉到80%,就在眼前也看不见,也因此导致了能力不再有较大的提升。

编程能力怎样会成长的最快

工作内容往往有些像在工厂🏭拧螺丝,大部分内容是重复的,也可以想象过去的一年你有过多少创新和学习了新的技能。那么这时候一般为了多学些内容会买一些技术书籍,但!技术类书籍和其他书籍不同,只要不去用看了也就只是轻描淡写,很难接纳和理解。就像设计模式,虽然可能看了几遍,但是在实际编码中仍然很少会用,大部分原因还是没有认认真真的跟着实操。事必躬亲才是学习编程的最好是方式。

二、开发环境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三个,可以通过关注公众号bugstack虫洞栈,回复源码下载获取(打开获取的链接,找到序号18)
工程描述
itstack-demo-design-11-01使用一坨代码实现业务需求
itstack-demo-design-11-02通过设计模式优化代码结构,减少内存使用和查询耗时

三、享元模式介绍

享元模式,图片来自 refactoringguru.cn

  • 图片来自:https://refactoringguru.cn/design-patterns/flyweight

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

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

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

四、案例场景模拟

场景模拟;秒杀场景下商品查询

在这个案例中我们模拟在商品秒杀场景下使用享元模式查询优化

你是否经历过一个商品下单的项目从最初的日均十几单到一个月后每个时段秒杀量破十万的项目。一般在最初如果没有经验的情况下可能会使用数据库行级锁的方式下保证商品库存的扣减操作,但是随着业务的快速发展秒杀的用户越来越多,这个时候数据库已经扛不住了,一般都会使用redis的分布式锁来控制商品库存。

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

这里我们模拟使用享元模式工厂结构,提供活动商品的查询。活动商品相当于不变的信息,而库存部分属于变化的信息。

五、用一坨坨代码实现

逻辑很简单,就怕你写乱。一片片的固定内容和变化内容的查询组合,CV的哪里都是!

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

1. 工程结构

itstack-demo-design-11-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── ActivityController.java
  • 以上工程结构比较简单,之后一个控制类用于查询活动信息。

2. 代码实现

/**
 * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
 * 公众号:bugstack虫洞栈
 * Create by 小傅哥(fustack) @2020
 */
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中获取活动的库存,而不是从库中,否则将造成数据不统一的问题。

六、享元模式重构代码

接下来使用享元模式来进行代码优化,也算是一次很小的重构。

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

1. 工程结构

itstack-demo-design-11-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── util
    │           │	└── RedisUtils.java	
    │           ├── Activity.java
    │           ├── ActivityController.java
    │           ├── ActivityFactory.java
    │           └── Stock.java
    └── test
        └── java
            └── org.itstack.demo.test
                └── ApiTest.java

享元模式模型结构

享元模式模型结构

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

2. 代码实现

2.1 活动信息
/**
 * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
 * 公众号:bugstack虫洞栈
 * Create by 小傅哥(fustack) @2020
 */
public class Activity {

    private Long id;        // 活动ID
    private String name;    // 活动名称
    private String desc;    // 活动描述
    private Date startTime; // 开始时间
    private Date stopTime;  // 结束时间
    private Stock stock;    // 活动库存
    
    // ...get/set
}
  • 这里的对象类比较简单,只是一个活动的基础信息;id、名称、描述、时间和库存。
2.2 库存信息
public class Stock {

    private int total; // 库存总量
    private int used;  // 库存已用
    
    // ...get/set
}
  • 这里是库存数据我们单独提供了一个类进行保存数据。
2.3 享元工厂
/**
 * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
 * 公众号:bugstack虫洞栈
 * Create by 小傅哥(fustack) @2020
 */
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中,可以按需选择。
2.4 模拟Redis类
/**
 * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
 * 公众号:bugstack虫洞栈
 * Create by 小傅哥(fustack) @2020
 */
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的操作工具类外,还提供了一个定时任务用于模拟库存的使用,这样方面我们在测试的时候可以观察到库存的变化。
2.4 活动控制类
/**
 * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
 * 公众号:bugstack虫洞栈
 * Create by 小傅哥(fustack) @2020
 */
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;
    }

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

3. 测试验证

3.1 编写测试类
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);
        }
    }

}
  • 这里我们通过活动查询控制类,在for循环的操作下查询了十次活动信息,同时为了保证库存定时任务的变化,加了睡眠操作,实际的开发中不会有这样的睡眠。
3.2 测试结果
22:35:20.285 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":1},"stopTime":1592130919931}
22:35:21.634 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":18},"stopTime":1592130919931}
22:35:22.838 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":30},"stopTime":1592130919931}
22:35:24.042 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":42},"stopTime":1592130919931}
22:35:25.246 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":54},"stopTime":1592130919931}
22:35:26.452 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":66},"stopTime":1592130919931}
22:35:27.655 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":78},"stopTime":1592130919931}
22:35:28.859 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":90},"stopTime":1592130919931}
22:35:30.063 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":102},"stopTime":1592130919931}
22:35:31.268 [main] INFO  org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":114},"stopTime":1592130919931}

Process finished with exit code 0
  • 可以仔细看下stock部分的库存是一直在变化的,其他部分是活动信息,是固定的,所以我们使用享元模式来将这样的结构进行拆分。

七、总结

  • 关于享元模式的设计可以着重学习享元工厂的设计,在一些有大量重复对象可复用的场景下,使用此场景在服务端减少接口的调用,在客户端减少内存的占用。是这个设计模式的主要应用方式。
  • 另外通过map结构的使用方式也可以看到,使用一个固定id来存放和获取对象,是非常关键的点。而且不只是在享元模式中使用,一些其他工厂模式、适配器模式、组合模式中都可以通过map结构存放服务供外部获取,减少ifelse的判断使用。
  • 当然除了这种设计的减少内存的使用优点外,也有它带来的缺点,在一些复杂的业务处理场景,很不容易区分出内部和外部状态,就像我们活动信息部分与库存变化部分。如果不能很好的拆分,就会把享元工厂设计的非常混乱,难以维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1792031.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

flutter as连接网易模拟器

网易模拟器下载 Mac 使用MuMu模拟器调试 Flutter开发 Android Studio 安装第三方模拟器—网易MuMu Mac 安卓Studio使用外部模拟器 Mac电脑&#xff1a;Android Studio 连接 MUMU 网易模拟器 Mac 上 Android Studio 链接网易 MuMu 模拟器调试 在 .zshrc 中设置 adb 二进制文…

计算机毕业设计 | 基于node(Koa)+vue 高校宿舍管理系统 宿舍可视化(附源码)

1&#xff0c;绪论 1.1 项目背景 随着科技的发展&#xff0c;智能化管理越来越重要。大学生在宿舍的时间超过了1/3&#xff0c;因此良好的宿舍管理对学生的生活和学习极为关键。学生宿舍管理系统能够合理安排新生分配宿舍&#xff0c;不浪费公共资源&#xff0c;减轻学校管理…

VBA excel 表格将多行拆分成多个表格或 文件 或者合并 多个表格

excel 表格 拆分 合并 拆分工作表按行拆分为工作表工作表按行拆分为工作薄 合并操作步骤 拆分 为了将Excel中的数万行数据拆分成多个个每个固定行数的独立工作表&#xff0c;并且保留每个工作表的表头&#xff0c;你可以使用以下VBA脚本。这个脚本会复制表头到每个新的工作表&…

opencv进阶 ——(十)图像处理之基于dlib人脸检测与识别

Dlib是一个功能丰富的C库&#xff0c;设计用于构建复杂的软件系统&#xff0c;尤其在机器学习、计算机视觉和数值计算等领域有着广泛的应用。以下是对Dlib的简要介绍&#xff1a; 特性&#xff1a; 机器学习算法&#xff1a;Dlib包含了各种机器学习算法&#xff0c;如支持向量机…

java分布式的ACP是什么

ACP 1、ACP是什么 一致性&#xff08;Consistency&#xff09;&#xff1a;在分布式系统中&#xff0c;当更新操作完成之后&#xff0c;所有节点在同一时间看到的数据是一致的。换句话说&#xff0c;对于任何数据的读取&#xff0c;都会得到最后写入的数据。可用性&#xff0…

鸿蒙开发接口安全:【@ohos.userIAM.userAuth (用户认证)】

用户认证 说明&#xff1a; 本模块首批接口从API version 6开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import userIAM_userAuth from ohos.userIAM.userAuth;完整示例 // API version 6 import userIAM_userAuth from ohos.use…

学习笔记——路由网络基础——静态路由(static)

三、静态路由(static) 1、静态路由 (1)定义 静态路由(Static)&#xff1a;由管理员手动配置和维护的路由。静态路由配置简单&#xff0c;被广泛应用于网络中。此外还可以实现负载均衡和路由备份。 静态路由默认优先级为60&#xff0c;如果想在多条静态路由中让某条路由优选…

图片裁剪与上传处理方案 —— 基于阿里云 OSS 处理用户资料

目录 01: 通用组件&#xff1a;input 构建方案分析 02: 通用组件&#xff1a;input 构建方案 03: 构建用户资料基础样式 04: 用户基本资料修改方案 05: 处理不保存时的同步问题 06: 头像修改方案流程分析 07: 通用组件&#xff1a;Dialog 构建方案分析 08: 通用组件&…

宝兰德参编!《2023年中国数据库年度行业分析报告》正式发布

近日&#xff0c;墨天轮发布 《2023年中国数据库年度行业分析报告》&#xff08;以下简称《报告》&#xff09;。宝兰德深度参与《报告》重要章节内容的编写工作&#xff0c;凭借在中间件领域深厚的技术沉淀和丰富的实践经验&#xff0c;输出了大量具有专业性和前瞻性的意见&am…

PHP实现抖音小程序用户登录获取openid

目录 第一步、抖音小程序前端使用tt.login获取code 第二步、前端拿到code传给后端 第三步、方法1 后端获取用户信息 第四步、方法2 抖音小程序拿到用户信息把用户信息传给后端 code2Session抖音小程序用户登录后端文档 第一步、抖音小程序前端使用tt.login获取code 前端 …

如何以抛物线形式排列一个列表,曲线排列 x² = y

如何以抛物线形式排列一个列表&#xff0c;曲线排列 一、需求 做页面的时候遇到一个需求&#xff0c;需要将一个列表以曲线的形式排列展示。 列表内容&#xff1a; const statisticLabels: Array<{name: string,icon: string,path: string,type: string,dataName: strin…

14-alert\confirm\prompt\自定义弹窗

一、认识alert\confirm\prompt 下图依次是alert、confirm、prompt&#xff0c;先认清楚长什么样子&#xff0c;以后遇到了就知道如何操作了。 二、alert操作 先用driver.switch_to.alert方法切换到alert弹出框上&#xff1b;可以用text方法获取弹出的文本信息&#xff1b;acce…

【Qt】定时器播放多张图片,动画效果

1. 效果 2. 代码 2.1 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);~Widget();void initGif(QS…

RPA-UiBot6.0数据采集机器人(海量信息一网打尽)内附RPA师资培训课程

前言 友友们是否曾为海量的数据信息而头疼&#xff0c;不知道如何从中精准抓取你所需的数据&#xff1f;小北的这篇博客将为你揭晓答案&#xff0c;让我们一起学习如何运用RPA数据采集机器人&#xff0c;轻松实现海量信息的快速抓取与整理&#xff0c;助力你的工作效率翻倍&…

用cocos2d-python绘制游戏开发的新篇章

用cocos2d-python绘制游戏开发的新篇章 第一部分&#xff1a;背景 在游戏开发的世界中&#xff0c;寻找一个强大而灵活的框架至关重要。cocos2d-python是一个Python游戏开发框架&#xff0c;它提供了一套丰富的功能&#xff0c;用于创建2D游戏、图形和交互式应用。基于流行的c…

掌握SVG基础:从零开始学习

格栅图可以实现图片的清晰显示&#xff0c;但这也意味着如果要在各种设备上使用格栅图&#xff0c;就会增加大量不同规格的格栅图&#xff0c;以适应各种尺寸的设备。这也直接导致资源文件体积的增加&#xff0c;矢量图没有这个问题。本文将SVG代码编写与即时设计工具相结合&am…

2024050401-重学 Java 设计模式《实战代理模式》

重学 Java 设计模式&#xff1a;实战代理模式「模拟mybatis-spring中定义DAO接口&#xff0c;使用代理类方式操作数据库原理实现场景」 一、前言 难以跨越的瓶颈期&#xff0c;把你拿捏滴死死的&#xff01; 编程开发学习过程中遇到的瓶颈期&#xff0c;往往是由于看不到前进…

[vue2项目]vue2+supermap[mapboxgl]+天地图之地图的初始化

Supermap参考教程 天地图 一、安装 1、终端:npm install supermap/vue-iclient-mapboxgl 2、在package.json文件的dependencies查看supermap/vue-iclient-mapboxgl依赖是否安装成功。 3、在mian.js全局引入 import VueiClient from supermap/vue-iclient-mapboxgl; Vue.use(…

[Classifier-Free] Classifier-Free Diffusion Guidance

1、背景 1&#xff09;Classifier Guidance的问题 a&#xff09;需要额外训练一个分类器&#xff08;要基于噪声图像训练&#xff0c;因此无法用现成的预训练分类器&#xff09;&#xff0c;使得扩散模型的训练pipeline更加复杂 b&#xff09;whether classifier guidance is s…

Vue05-数据绑定

一、数据绑定 1-1、v-bind指令 1-2、v-model指令 1、单项数据绑定&#xff1a; 2、双向数据绑定 注意&#xff1a; 表单元素&#xff0c;必须要有属性&#xff1a;value&#xff01;&#xff01;&#xff01; 1-3、小结