一、享元模式介绍 1. 解决的问题 主要解决在有大量对象时,可能造成内存溢出的问题。
2. 定义 享元模式是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,能在有限的内存容量中载入更多对象。
3. 应用场景
在程序必须支持大量对象且没有足够的内存容量时使用享元模式。
二、享元模式优缺点 1. 优点
2. 缺点
可能需要牺牲执行速度来换取内存,因为每次调用向享元方法时都需要重新计算部分情景数据。
代码会变得更加复杂。如:为什么要拆分一个实体的状态?
三、享元模式应用实例:高校选课系统优化 1. 实例场景 大家在学校选课时,应该都遇到过高峰期选课系统无法使用,只能眼巴巴地等技术人员修复。
为什么选课系统说崩就崩呢?有没有一些简单的方法就能对这类场景做优化呢?
还真有!选课系统主要就是要获取我们的选课信息,在我们填写选课信息后,提交到服务器,由服务起处理选课信息。
一般来讲,选课系统应该都是为每个学生选择的每个课程创建一个对象,然后提交到服务器。
这种设计其实在访问量不大的情况下没有什么问题,但高并发情况下,假设每个人的课程信息对象平均大小为 1M ,500 个选课请求仅课程信息对象的内存就达到 500M ,浪费了大量资源,这种大量内存消耗也容易导致 Out Of Memory。
接下来,就以选课系统作为场景,介绍一下如何用享元模式优化。
2. 享元模式实现 2.1 工程结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 flyweight-pattern └─ src ├─ main │ └─ java │ └─ org.design.pattern.flyweight │ ├─ model │ │ ├─ Course.java │ │ └─ Student.java │ ├─ factory │ │ └─ CourseFactory.java │ └─ service │ ├─ CourseSelectionService.java │ └─ impl │ └─ CourseSelectionServiceImpl.java └─ test └─ java └─ org.design.pattern.flyweight.test └─ CourseSelectionTest.java
2.2 代码实现 2.2.1 实体 课程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @AllArgsConstructor @Getter public class Course { private String id; private String name; private String credit; }
学生
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Getter @AllArgsConstructor public class Student { private String id; private String name; }
2.2.2 工厂 课程工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class CourseFactory { private static final Logger log = LoggerFactory.getLogger(CourseFactory.class); private static Map<String, Course> courseMap = new ConcurrentHashMap <String, Course>(); public static Course getCourse (Map<String, String> dataMap) { String id = dataMap.get("id" ); if (ObjectUtils.isEmpty(id)) { log.warn("course id is empty" ); return null ; } if (ObjectUtils.isNotEmpty(courseMap.get(id))) { log.info("get cache course {}" , id); return courseMap.get(id); } else { log.info("create new course {}" , id); return createCourse(id, dataMap.get("name" ), dataMap.get("credit" )); } } public static Course createCourse (String id, String name, String credit) { if (ObjectUtils.isNotEmpty(id) && ObjectUtils.isNotEmpty(name) && ObjectUtils.isNotEmpty(credit)) { Course course = new Course (id, name, credit); courseMap.put(id, course); return course; } return null ; } }
2.2.3 服务 选课服务接口
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface CourseSelectionService { void selectCourse (Student student, Map<String, String> courseDataMap) ; }
选课服务实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class CourseSelectionServiceImpl implements CourseSelectionService { private static final Logger log = LoggerFactory.getLogger(CourseFactory.class); @Override public void selectCourse (Student student, Map<String, String> courseDataMap) { Course course = CourseFactory.getCourse(courseDataMap); if (ObjectUtils.isEmpty(course)) { log.error("course is not exist" ); return ; } log.info( "student {} select {} course, the course credit is {}" , student.getName(), student.getName(), course.getCredit() ); } }
2.3 测试验证 2.3.1 测试验证类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class CourseSelectionTest { private CourseSelectionService courseSelectionService = new CourseSelectionServiceImpl (); @Test public void testSelectCourse () { Map<String, String> courseDataMap = new HashMap <String, String>() { { put("id" , "1" ); put("name" , "高数一" ); put("credit" , "5" ); } }; Student studentA = new Student ("1" , "张三" ); courseSelectionService.selectCourse(studentA, courseDataMap); Student studentB = new Student ("2" , "李四" ); courseSelectionService.selectCourse(studentB, courseDataMap); } }
2.3.2 测试结果 1 2 3 4 5 6 17:22:37.377 [main] INFO o.d.p.f.factory.CourseFactory - create new course 1 17:22:37.380 [main] INFO o.d.p.f.factory.CourseFactory - student 张三 select 张三 course, the course credit is 5 17:22:37.380 [main] INFO o.d.p.f.factory.CourseFactory - get cache course 1 17:22:37.380 [main] INFO o.d.p.f.factory.CourseFactory - student 李四 select 李四 course, the course credit is 5 Process finished with exit code 0
四、享元模式结构
享元模式只是一种优化。在应用该模式之前,要确定程序中存在大量类似对象同时占用内存相关的内存消耗问题,并且确保该问题无法使用其他更好的方式来解决。
享元 (Flyweight)类包含原始对象中部分能在多个对象中共享的状态。
同一享元对象可在许多不同情景中使用。享元中存储的状态被称为 “内在状态” 。传递给享元方法的状态被称为 “外在状态” 。
情景 (Context)类包含原始对象中各不相同的外在状态。
情景与享元对象组合在一起就能表示原始对象的全部状态。
通常情况下,原始对象的行为会保留在享元类中。因此享元方法必须提供部分外在状态作为参数。但也可以将行为移动到情景类中,然后将连入的享元作为单纯的数据对象。
客户端 (Client)负责计算或者存储享元的外在状态。在客户端看来,享元是一种可在运行时进行配置的模板对象,具体的配置方式为向其方法中传入一些情景数据参数。
享元工厂 (Flyweight Factory)会对已有享元的缓存池进行管理。
有了工厂后,客户端就无需直接创建享元,只需调用工厂并向其传递目标享元的一些内在状态即可。工厂会根据参数在之前已创建的享元中进行查找,找到满足条件的享元将其返回,否则根据参数新建享元。
设计模式并不难学,其本身就是多年经验提炼出的开发指导思想,关键在于多加练习,带着使用设计模式的思想去优化代码,就能构建出更合理的代码。
源码地址:https://github.com/yiyufxst/design-pattern-java
参考资料: 小博哥重学设计模式:https://github.com/fuzhengwei/itstack-demo-design 深入设计模式:https://refactoringguru.cn/design-patterns/catalog