工作中常用的设计模式 -- 享元模式
一般做业务开发,不太容易有大量使用设计模式的场景。这里总结一下在业务开发中使用较为频繁的设计模式。当然语言为 Java,基于 Spring 框架。
1 享元模式(Flyweight Pattern)
如果系统中存在大量的重复对象,或者需要不断地创建和销毁大量的重复对象。这时就可以使用该模式,缓存这些重复的对象,达到共享的目的,从而大大减少内存占用。
享元模式一般有 3 个角色:
Flyweight: 享元类(主要定义内部状态与外部状态)
FlyweightFactory: 享元工厂类(主要用于创建和管理享元类)
1.1 实际业务场景
这是由实际生产事故引发,反向推动使用的设计模式。因为做营销相关,很多用户留资页(落地页)都会使用到国家地区及电话国家码。作为基础数据,我们这边并没有做存储,甚至当时连缓存也没做。完全由基础服务组提供相关Feign
接口调用,说白了我们就是个中间商。唯一做的就是要根据用户 IP 来识别当前国家地区、以及存储 Top15 热点国家地区数据。
这一份数据,我记得当时统计的有30KB
左右。也就是说每个请求,都会在堆中创建这么一个对象,紧接着就成为了垃圾对象。当时是在做一个问卷调查,会给所有 APP 用户推送。服务器最开始只有一台无响应,紧接着又第二台,运维边重启机器边在边在告警群通知。
经排查,并结合最近上线内容,及接口请求监控数据。认定是由于国家地区接口访问量突增引起的。但具体原因及解决方案还需要看具体代码逻辑才能确定。
介绍完业务背景,咱们来说下大概的数据结构。
地区元数据
整体结构
在这份数据中,很容易看到。allCountry
基本不会改变,可以认为是内部状态,当然这也是占用空间最大的地方。commonCountry
在一定时间内基本不会改变,但我们在这把他作为一个外部状态处理(内部实现可通过缓存或其他方式实现)。currentCountry
需要根据请求 IP 来动态生成,这个作为外部变量处理。至此,整体思路已清晰。
1.2 代码实现
国家信息 POJO
享元类(内部状态、外部状态)
享元工厂类
因为本例的享元类中,内部状态只有一种。所以增加了DEFAULT
key 值,实际也就这一种。在这里有没有发现什么???如果没有这个DEFAULT
,这不就是个单例模式吗???这块之所以这样写,主要为了更好的理解享元模式本身。
CountryClient
为模拟外部 Feign 接口服务,提供全量国家数据获取服务。并通过@PostConstruct
将数据注入到静态字典中。通过该工厂类,就可以拿到CountryList
,在根据需要将外部状态
注入该类,就可以使用了。
1.4 单测
2 思考
跟单例模式有啥区别?
单例主要在于
单
,即全局仅有一个实例;而享元主要在于享
,即共享。在一定程度上,可以认为单例是享元的一种特殊情况。
享元更主要的是通过共享,达到节约内存的目的。
跟缓存有些类似?
缓存,主要目的是为了加快访问速度。享元,在于数据共享节约空间为目的。
缓存是一种思想,可以通过享元模式来实现缓存。
其他的一些思考?
享元类似单例,这就可能会涉及到线程安全问题?
主要在于看问题的角度,并不冲突。涉及到并发,要考虑线程安全问题。
不通过享元模式,也能达到相同的效果?
设计模式是对特定(某些场景)通用的解决方案,变通也是很重要的。先有问题才有解决方案,而不是先有设计模式才有特定场景。
封面图来源:https://refactoring.guru/design-patterns/flyweight
版权声明: 本文为 InfoQ 作者【lpe234】的原创文章。
原文链接:【http://xie.infoq.cn/article/5ca394925113afc03f5485245】。未经作者许可,禁止转载。
评论