当前位置: 首页 > news >正文

模式组合应用-享元模式

写在前面

Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!


享元模式

定义

结构型设计模式, 通过共享大量细粒度对象来最小化内存使用和计算开销。当系统中存在大量重复对象, 且这些对象的大部分状态可以外部化时, 享元模式通过共享这些对象的 内在状态 来减少对象的创建数量, 从而优化性能和资源利用。

基本结构

享元模式的核心思想是 分离内在状态与外在状态

  • 内在状态: 可被共享的、不随环境改变而改变的属性, 这些属性是享元对象固有的, 并且在享元对象之间共享。

  • 外在状态: 指不可被共享的、随环境改变而该百年的属性, 这些属性由客户端独立维护, 并在需要时传入享元对象。

使用时主要思考点
  • 识别内在及外在状态, 需要仔细分析对象的属性, 区别哪些是可以被共享的, 哪些是不可被共享的。内在状态是享元对象的核心, 而外在状态则由客户端负责管理和传递。

  • 由于享元模式是通过减少对象数量来节省内存, 并引入了享元工厂和状态分离的复杂性。在某些情况下, 如果共享的对象数量不多, 或者对象的内存占用很小, 引入享元模式将带来负面效果。

  • 享元工厂负责管理和创建享元对象, 其内部应该维护一个享元对象的池, 并在客户端请求时提供享元对象。工厂实现通常需要考虑线程安全问题。

  • 为保证享元对象内在状态能够被安全的共享, 享元对象的内在状态应该是不可变的。一旦享元对象被创建, 其内在状态就不应该再被修改。


享元模式+工厂方法模式

工厂方法模式

创建型设计模式, 定义了一个用于创建对象的接口, 但由子类决定实例化哪一个类, 使得类的实例化延迟到子类。

解决了直接实例化对象带来的高耦合问题, 使得系统在增加新的产品时, 无需修改客户端代码, 只需要增加新的具体产品类和对应的具体工厂类即可。

案例

在一个大型在线教育平台中, 存在大量的课程资源, 如视频、文档、测验等。为了提供用户体验, 平台需要为每种资源提供一个预览功能。不同类型的资源有不同的预览逻辑, 例如: 视频资源需要播放器预览, 文档资源需要文档阅读器预览, 测验资源需要交互式界面预览。同时, 当平台用户量变大时, 同一时间可能有大量用户预览各种资源, 如果每次预览都创建新的资源对象, 将导致巨大的内存开销。使用享元模式进行编码, 以解决上述问题。

模式职责

  • 享元模式: 负责共享课程资源的内在状态(如资源类型、通用预览逻辑), 避免重复创建大量相同的资源对象

  • 工厂方法模式: 根据不同的课程资源类型, 创建对应的具体享元对象, 将对象的创建与使用解耦。

代码结构

抽象享元角色
public interface CourseResource {void preview(String extrinsicState);String getType();}
  • 定义了所有具体享元对象必须实现的接口。

  • preview 方法接收一个 extrinsicState 参数, 用于标识外在状态。

  • getType() 用于获取享元对象的内在状态(类型)。

具体享元角色
public class DocumentResource implements CourseResource {private final String type;public DocumentResource(String type) {this.type = type;System.out.println("创建了一个新的文档资源对象: " + type);}@Overridepublic void preview(String extrinsicState) {System.out.println("正在预览 " + type + " 文档, 页码: " + extrinsicState);}@Overridepublic String getType() {return type;}
}public class QuizResource implements CourseResource {private final String type;public QuizResource(String type) {this.type = type;System.out.println("创建了一个新的测验资源对象: " + type);}@Overridepublic void preview(String extrinsicState) {System.out.println("正在预览 " + type + " 测验, 题目: " + extrinsicState);}@Overridepublic String getType() {return type;}
}public class VideoResource implements CourseResource {private final String type;public VideoResource(String type) {this.type = type;System.out.println("创建了一个新的视频资源对象: " + type);}@Overridepublic void preview(String extrinsicState) {System.out.println("正在预览 " + type + " 视频, 进度: " + extrinsicState);}@Overridepublic String getType() {return type;}
}
  • 实现了 CourseResource 接口, 并实现各自内在状态 和 预览逻辑。

  • DocumentResource(文档资源享元类)、QuizResource(测验资源享元类)、VideoResource(视频资源享元类)。

享元工厂角色
public class CourseResourceFactory {private static final Map<String, CourseResource> resourceMap = new HashMap<>();public static CourseResource getResource(String type) {CourseResource resource = resourceMap.get(type);if (resource == null) {switch (type) {case "Video":resource = new VideoResource(type);break;case "Document":resource = new DocumentResource(type);break;case "Quiz":resource = new QuizResource(type);break;default:throw new IllegalArgumentException("未知资源类型: " + type);}resourceMap.put(type, resource);}return resource;}public static int getResourceCount() {return resourceMap.size();}}
  • getResource 方法负责管理和提供享元对象(CourseResource), 若存在则返回, 若不存在则创建一个新的具体享元对象, 并将其放入 resourceMap 中。

  • getResourceCount 用于验证当前工厂中实际创建的享元对象数量。


抽象工厂角色
public interface ResourcePreviewerFactory {CourseResource createResource();}
  • 定义了一个抽象的工厂方法 createResource(), 方法返回一个 CourseResource 类型的对象, 接口将对象的创建过程抽象化, 使得客户端无需关心具体对象的创建细节。

具体工厂角色
public class VideoPreviewerFactory implements ResourcePreviewerFactory {@Overridepublic CourseResource createResource() {return CourseResourceFactory.getResource("Video");}
}
public class QuizPreviewerFactory implements ResourcePreviewerFactory {@Overridepublic CourseResource createResource() {return CourseResourceFactory.getResource("Quiz");}
}
public class DocumentPreviewerFactory implements ResourcePreviewerFactory {@Overridepublic CourseResource createResource() {return CourseResourceFactory.getResource("Document");}
}
  • 实现了 ResourcePreviewerFactory 接口, 每个具体工厂负责创建特定 CourseResource 类型的对象, 通过享元工厂进行对象的创建( CourseResourceFactory.getResource() )。


测试类
public class FlyweightTest {@Testpublic void test_courseResource() {ResourcePreviewerFactory videoFactory = new VideoPreviewerFactory();ResourcePreviewerFactory documentFactory = new DocumentPreviewerFactory();ResourcePreviewerFactory quizFactory = new QuizPreviewerFactory();System.out.println("\n用户A预览视频: ");CourseResource video1 = videoFactory.createResource();video1.preview("10%");System.out.println("\n用户B预览视频: ");CourseResource video2 = videoFactory.createResource();video2.preview("50%");System.out.println("\n用户C预览文档: ");CourseResource document1 = documentFactory.createResource();document1.preview("第3页");System.out.println("\n用户D预览文档: ");CourseResource document2 = documentFactory.createResource();document2.preview("第10页");System.out.println("\n用户E预览测验: ");CourseResource quiz1 = quizFactory.createResource();quiz1.preview("第1题");System.out.println("\n用户F预览测验");CourseResource quiz2 = quizFactory.createResource();quiz2.preview("第5题");}}
运行结果
用户A预览视频: 
创建了一个新的视频资源对象: Video
正在预览 Video 视频, 进度: 10%用户B预览视频: 
正在预览 Video 视频, 进度: 50%用户C预览文档: 
创建了一个新的文档资源对象: Document
正在预览 Document 文档, 页码: 第3页用户D预览文档: 
正在预览 Document 文档, 页码: 第10页用户E预览测验: 
创建了一个新的测验资源对象: Quiz
正在预览 Quiz 测验, 题目: 第1题用户F预览测验
正在预览 Quiz 测验, 题目: 第5题Process finished with exit code 0

组合优势

  • 享元模式专注于对象的共享和内存优化, 而工厂方法模式专注于对象的创建过程, 各司其职, 易于理解和维护。

  • 工厂模式将对象的创建逻辑集中到 CourseResourceFactory 中, 使得客户端无需关心具体享元对象的创建细节。同时也负责享元对象的复用, 确保了享元模式的有效实施。


享元模式+策略模式

策略模式(行为型)

定义了一系列算法, 并将每个算法封装起来, 使得他们可以相互替换, 策略模式使得算法可以独立于使用它的客户端而变化。

案例

假设我们正在开发一个大型电商平台的订单系统。该平台每天会进行大量的促销活动,包括各种折扣、满减、秒杀等。用户在购物时,购物车中的商品总价需要根据当前生效的促销策略进行计算。由于平台用户量巨大,同时进行的订单和购物车数量也非常庞大,而且许多促销策略(例如“全场9折”、“满200减20”)会被大量用户同时使用。

模式职责

  • 享元模式: 负责共享价格计算策略的内在状态(即策略本身), 减少策略对象的创建数量。

  • 策略模式: 负责定义价格计算的算法族, 使得客户端可以灵活地选择和切换不同的价格计算策略。

代码结构

抽象享元/抽象策略
public interface PriceCalculationStrategy {double calculatePrice(double originalPrice);
}
  • 定义了所有价格计算策略的公共接口 calculatePrice(double originalPrice), 所有具体策略必须实现, 用于计算最终价格。

抽象享元/抽象策略
public class DiscountStrategy implements PriceCalculationStrategy {// 内在状态 折扣率private double discountRate;public DiscountStrategy(double discountRate) {this.discountRate = discountRate;System.out.println("创建新的折扣策略享元: " + (discountRate * 100) + "%");}@Overridepublic double calculatePrice(double originalPrice) {return originalPrice * (1 - discountRate);}
}
public class FullReductionStrategy implements PriceCalculationStrategy {private double fullAmount;private double reductionAmount;public FullReductionStrategy(double fullAmount, double reductionAmount) {this.fullAmount = fullAmount;this.reductionAmount = reductionAmount;System.out.println("创建新的满减策略享元: 满" + fullAmount + " 减 " + reductionAmount);}@Overridepublic double calculatePrice(double originalPrice) {if (originalPrice >= fullAmount) {return originalPrice - reductionAmount;}return originalPrice;}
}
  • DiscountStrategy 打折价格计算策略, 实现了 PriceCalculationStrategy 接口, 并包含了 discountRate 作为内在状态, 相同 折扣率的 DiscountStrategy 对象可以被共享, 构造函数中打印信息用于验证享元对象的创建。

  • FullReductionStrategy 满减价格计算策略, 同样实现了 PriceCalculationStrategy 接口, 包含了 fullAmountreductionAmount 作为内在状态。

享元工厂
public class StrategyFactory {private static final Map<String, PriceCalculationStrategy> strategies = new HashMap<>();public static PriceCalculationStrategy getStrategy(String type, double... params) {String key = type + "-" + Arrays.toString(params);PriceCalculationStrategy strategy = strategies.get(key);if (strategy == null) {switch (type) {case "Discount":strategy = new DiscountStrategy(params[0]);break;case "FullReduction":strategy = new FullReductionStrategy(params[0], params[1]);break;default:throw new IllegalArgumentException("未知策略类型: " + type);}strategies.put(key, strategy);}return strategy;}}
  • 维护一个 Map 来存储已经创建的策略享元对象。

  • getStrategy 方法, 根据策略类型和参数生成一个唯一 key, 然后检查映射中是否存在, 若存在则返回; 否则创建新的策略对象, 并将其放入映射中。

策略上下文
public class ShoppingCart {private PriceCalculationStrategy strategy;private double totalAmount;public ShoppingCart(double totalAmount) {this.totalAmount = totalAmount;}public void setStrategy(PriceCalculationStrategy strategy) {this.strategy = strategy;}public double checkout() {if (strategy == null) {System.out.println("未设置价格计算策略, 按原价结算。");return totalAmount;}System.out.println("使用策略进行结算...");return strategy.calculatePrice(totalAmount);}}
  • 持有 PriceCalculationStrategy 的引用, 并包含 totalAmount 作为外在状态, 并负责调用其 calculatePrice 方法。

  • setStrategy 方法允许客户端动态地设置或切换价格计算策略。

  • checkout 方法委托当前设置的策略对象来计算最终价格。

测试类
public class PriceCalculationTest {@Testpublic void test_priceCalculation() {ShoppingCart cart1 = new ShoppingCart(200.0);cart1.setStrategy(StrategyFactory.getStrategy("Discount", 0.1));System.out.println("购物车1结算金额: " + cart1.checkout() + "\n");ShoppingCart cart2 = new ShoppingCart(180.0);cart2.setStrategy(StrategyFactory.getStrategy("FullReduction", 100.0, 50.0));System.out.println("购物车2结算金额: " + cart2.checkout() + "\n");ShoppingCart cart3 = new ShoppingCart(300.0);cart3.setStrategy(StrategyFactory.getStrategy("FullReduction", 100.0, 50.0));System.out.println("购物车3结算金额: " + cart3.checkout() + "\n");ShoppingCart cart4 = new ShoppingCart(500.0);cart4.setStrategy(StrategyFactory.getStrategy("Discount", 0.2));System.out.println("购物车4结算金额: " + cart4.checkout());}}
运行结果
创建新的折扣策略享元: 10.0%
使用策略进行结算...
购物车1结算金额: 180.0创建新的满减策略享元: 满100.0 减 50.0
使用策略进行结算...
购物车2结算金额: 130.0使用策略进行结算...
购物车3结算金额: 250.0创建新的折扣策略享元: 20.0%
使用策略进行结算...
购物车4结算金额: 400.0Process finished with exit code 0

组合优势

  • 通过享元模式, 相同的策略对象可以被多个上下文复用, 避免了重复创建, 提供了代码的复用性。

  • 享元模式解决了大量重复策略对象造成的内存消耗问题, 而策略模式则提供了灵活切换算法的能力。两者结合, 可以在管理多种算法的同时, 有效 奖励内存占用。

当前代码结构在技术上是合理的, 但从 "内存优化" 角度来看, 享元模式的引入存在 “过度设计”, 因为它增加了代码的复杂性而带来的收益并不明显。

http://www.dtcms.com/a/392608.html

相关文章:

  • 租房小程序房产小程序源码方案详解
  • p-value与e-value
  • 面经分享--京东一面
  • 大数据毕业设计选题推荐-基于大数据的帕金森病数据可视化分析系统-Spark-Hadoop-Bigdata
  • stack 和 queue
  • 执行yarn init报错:error Invalid package name.(question name)包名格式不对
  • Windows 下 PyTorch 入门深度学习环境安装与配置 CPU GPU 版 | 土堆教程
  • Transformer中为什么要使用多头注意力?
  • 《嵌入式硬件(十六):基于IMX6ULL的I2C的操作》
  • AI.工作助手.工作提效率
  • 【开题答辩全过程】以 Louis宠物商城为例,包含答辩的问题和答案
  • 微服务-网络模型与服务通信方式openfein
  • 如何快速定位局域网丢包设备?
  • 算法<java>——排序(冒泡、插入、选择、归并、快速、计数、堆、桶、基数)
  • 深入浅出CMMI:从混乱到卓越的研发管理体系化之路
  • Docker一键部署prometheus并实现飞书告警详解
  • 基于“开源AI大模型AI智能名片S2B2C商城小程序”的多平台资源位传播对直播营销流量转化的影响研究
  • 【设计模式】适配器模式 在java中的应用
  • 2013/07 JLPT听力原文 问题四
  • MyBatis 缓存体系剖析
  • MySQL 主从复制 + MyCat 读写分离 — 原理详解与实战
  • Vmake AI:美图推出的AI电商商品图编辑器,快速生成AI时装模特和商品图
  • Debian13 钉钉无法打开问题解决
  • 02.容器架构
  • Diffusion Model与视频超分(1):解读淘宝开源的视频增强模型Vivid-VR
  • 通过提示词工程(Prompt Engineering)方法重新生成从Ollama下载的模型
  • 有没有可以检测反爬虫机制的工具?
  • 大模型为什么需要自注意力机制?
  • 长度为K子数组中的最大和-定长滑动窗口
  • Linux安装Kafka(无Zookeeper模式)保姆级教程,云服务器安装部署,Windows内存不够可以看看