最新要闻
- 65寸4K大屏电视不到2000元 LCD白菜价即将结束:3月价格上涨10%
- 环球微头条丨最强AI再次进化 ChatGPT下周升级GPT-4:支持视频了
- 【环球聚看点】免费玩!《生化危机4:重制版》体验版上线:不限时、不限次
- 当前滚动:德国电动空中出租车Lilium jet完成测试:时速250km/h 全机36个电风扇
- 杀疯了!长安深夜放大招 购车百亿补贴:深蓝SL03直降2.2万
- 世界观点:佳兆业成今年首家复牌出险房企
- 全球球精选!一座河南小县城的全球钻石生意爆火:价格不到天然的1/3
- 我国再次成功发射一箭双星:天绘六号A/B星顺利进入预定轨道
- 当前快看:资助8年的女生毕业放弃工作 嫁给有钱人成家庭主妇 资助人:失眠好几天
- 天天通讯!上班族如何备考公务员_如何备考公务员
- 环球热议:用盆吃10袋泡面男子火了 回应月薪2万邀约:浇完家里18亩地再说
- 快讯:苹果iPhone 14黄色款预售:全新配色不加价 5999元起
- 天天微速讯:租客辞职要搬走被房东介绍工作 当事人感动
- 即时焦点:一辆腾势敢占两个充电桩:真不怕挨揍?
- “常温超导”把A股都晃晕了!真要改变我们的生活?
- 《霍格沃茨之遗》渗人面部Bug:玩家看了起鸡皮疙瘩
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
工厂模式进阶用法,如何动态选择对象?
前言
工厂设计模式可能是最常用的设计模式之一,我想大家在自己的项目中都用到过。可能你会不屑一顾,但这篇文章不仅仅是关于工厂模式的基本知识,更是讨论如何在运行时动态选择不同的方法进行执行,你们可以看看是不是和你们项目中用的一样?
欢迎关注个人公众号【JAVA旭阳】交流沟通
小菜鸟的问题
直接上例子说明,设计一个日志记录的功能,但是支持记录到不同的地方,例如:
(相关资料图)
- 内存中
- 磁盘上的文件
- 数据库
- 百度网盘等远程存储服务
面对这么一个需求,你会怎么做呢?我们先来看看小菜鸟的做法吧。
- 小菜鸟创建了一个
Logger
类
class Logger { public void log(String message, String loggerMedium) {}}
- 小菜鸟想都不想,直接一通
if else
。
class Logger { public void log(String message, String loggerMedium) { if (loggerMedium.equals("MEMORY")) { logInMemory(message); } else if (loggerMedium.equals("FILE")) { logOnFile(message); } else if (loggerMedium.equals("DB")) { logToDB(message); } else if (loggerMedium.equals("REMOTE_SERVICE")) { logToRemote(message); } } private void logInMemory(String message) { // Implementation } private void logOnFile(String message) { // Implementation } private void logToDB(String message) { // Implementation } private void logToRemote(String message) { // Implementation }}
现在突然说要增加一种存储介质FLASH_DRIVE
,就要改了这个类?不拍改错吗?也不符合“开闭原则”,而且随着存储介质变多,类也会变的很大,小菜鸟懵逼了,不知道怎么办?
有没有更好的方法呢?
这时候小菜鸟去找你帮忙,你一顿操作,改成了下面这样:
class InMemoryLog { public void logToMemory(String message) { // Implementation }}class FileLog { public void logToFile(String message) { //Implementation }}class DBLog { public void logToDB(String message) { // Implementation }}class RemoteServiceLog { public void logToService(String message) { // Implementation }}class Logger { private InMemoryLog mLog; private FileLog fLog; private DBLog dbLog; private RemoteServiceLog sLog; public Logger() { mLog = new InMemoryLog(); fLog = new FileLog(); dbLog = new DBLog(); sLog = new RemoteServiceLog(); } public void log(String message, String loggerMedium) { if (loggerMedium.equals("MEMORY")) { mLog.logToMemory(message); } else if (loggerMedium.equals("FILE")) { fLog.logToFile(message); } else if (loggerMedium.equals("DB")) { dbLog.logToDB(message); } else if (loggerMedium.equals("REMOTE_SERVICE")) { sLog.logToService(message); } }}
在这个实现中,你已经将单独的代码分离到它们对应的文件中,但是Logger
类与存储介质的具体实现紧密耦合,如FileLog
、DBLog
等。随着存储介质的增加,类中将引入更多的实例Logger
。
还有什么更好的办法吗?
你想了想,上面的实现都是直接写具体的实现类,是面向实现编程,更合理的做法是面向接口编程,接口意味着协议,契约,是一种更加稳定的方式。
- 定义一个日志操作的接口
public interface LoggingOperation { void log(String message);}
- 实现这个接口
class InMemoryLog implements LoggingOperation { public void log(String message) { // Implementation }}class FileLog implements LoggingOperation { public void log(String message) { //Implementation }}class DBLog implements LoggingOperation { public void log(String message) { // Implementation }}class RemoteServiceLog implements LoggingOperation { public void log(String message) { // Implementation }}
- 你定义了一个类,据传递的参数,在运行时动态选择具体实现,这就是所谓的工厂类,不过是基础版。
class LoggerFactory { public static LoggingOperation getInstance(String loggerMedium) { LoggingOperation op = null; switch (loggerMedium) { case "MEMORY": op = new InMemoryLog(); break; case "FILE": op = new FileLog(); break; case "DB": op = new DBLog(); break; case "REMOTE_SERVICE": op = new RemoteServiceLog(); break; } return op; }}
- 现在你的
Logger
类的实现就是下面这个样子了。
class Logger { public void log(String message, String loggerMedium) { LoggingOperation instance = LoggerFactory.getInstance(loggerMedium); instance.log(message); }}
这里的代码变得非常统一,创建实际存储实例的责任已经转移到LoggerFactory
,各个存储类只实现它们如何将消息记录到它们的特定介质,最后该类Logger
只关心通过LoggerFactory
将实际的日志记录委托给具体的实现。这样,代码就很松耦合了。你想要添加一个新的存储介质,例如FLASH_DRIVE
,只需创建一个实现LoggingOperation
接口的新类并将其注册到LoggerFactory
中就好了。这就是工厂模式可以帮助您动态选择实现的方式。
还能做得更好吗?
你已经完成了一个松耦合的设计,但是想象一下假如有数百个存储介质的场景,所以我们最终会在工厂类LoggerFactory
中的switch case
部分case
数百个。这看起来还是很糟糕,如果管理不当,它有可能成为技术债务,这该怎么办呢?
摆脱不断增长的if else
或者 switch case
的一种方法是维护类中所有实现类的列表,LoggerFactory
代码如下所示:
class LoggerFactory { private static final List instances = new ArrayList<>(); static { instances.addAll(Arrays.asList( new InMemoryLog(), new FileLog(), new DBLog(), new RemoteServiceLog() )); } public static LoggingOperation getInstance(ApplicationContext context, String loggerMedium) { for(LoggingOperation op : instances) { // 比如判断StrUtil.equals(loggerMedium, op.getType()) op本身添加一个type } return null; }}
但是请注意,还不够,在所有上述实现中,无论if else、switch case
还是上面的做法,都是让存储实现与LoggerFactory
紧密耦合的。你添加一种实现,就要修改LoggerFactory
,有什么更好的做法吗?
逆向思维一下,我们是不是让具体的实现主动注册上来呢?通过这种方式,工厂不需要知道系统中有哪些实例可用,而是实例本身会注册并且如果它们在系统中可用,工厂就会为它们提供服务。具体代码如下:
class LoggerFactory { private static final Map instances = new HashMap<>(); public static void register(String loggerMedium, LoggingOperation instance) { if (loggerMedium != null && instance != null) { instances.put(loggerMedium, instance); } } public static LoggingOperation getInstance(String loggerMedium) { if (instances.containsKey(loggerMedium)) { return instances.get(loggerMedium); } return null; }}
在这里,LoggerFactory
提供了一个register
注册的方法,具体的存储实现可以调用该方法注册上来,保存在工厂的instances
map对象中。
我们来看看具体的存储实现注册的代码如下:
class RemoteServiceLog implements LoggingOperation { static { LoggerFactory.register("REMOTE", new RemoteServiceLog()); } public void log(String message) { // Implementation }}
由于注册应该只发生一次,所以它发生在static
类加载器加载存储类时的块中。
但是又有一个问题,默认情况下JVM不加载类RemoteServiceLog
,除非它由应用程序在外部实例化或调用。因此,尽管存储类有注册的代码,但实际上注册并不会发生,因为没有被JVM加载,不会调用static代码块中的代码, 你又犯难了。
你灵机一动,LoggerFactory
是获取存储实例的入口点,能否在这个类上做点文章,就写下了下面的代码:
class LoggerFactory { private static final Map instances = new HashMap<>(); static { try { loadClasses(LoggerFactory.class.getClassLoader(), "com.alvin.storage.impl"); } catch (Exception e) { // log or throw exception. } } public static void register(String loggerMedium, LoggingOperation instance) { if (loggerMedium != null && instance != null) { instances.put(loggerMedium, instance); } } public static LoggingOperation getInstance(String loggerMedium) { if (instances.containsKey(loggerMedium)) { return instances.get(loggerMedium); } return null; } private static void loadClasses(ClassLoader cl, String packagePath) throws Exception { String dottedPackage = packagePath.replaceAll("[/]", "."); URL upackage = cl.getResource(packagePath); URLConnection conn = upackage.openConnection(); String rr = IOUtils.toString(conn.getInputStream(), "UTF-8"); if (rr != null) { String[] paths = rr.split("\n"); for (String p : paths) { if (p.endsWith(".class")) { Class.forName(dottedPackage + "." + p.substring(0, p.lastIndexOf("."))); } } } }}
在上面的实现中,你使用了一个名为loadClasses
的方法,该方法扫描提供的包名称com.alvin.storage.impl
并将驻留在该目录中的所有类加载到类加载器。以这种方式,当类加载时,它们的static
块被初始化并且它们将自己注册到LoggerFactory
中。
如何在 SpringBoot 中实现此技术?
你突然发现你的应用是springboot应用,突然想到有更方便的解决方案。
因为你的存储实现类都被标记上注解@Component
,这样 Spring
会在应用程序启动时自动加载类,它们会自行注册,在这种情况下你不需要使用loadClasses
功能,Spring
会负责加载类。具体的代码实现如下:
class LoggerFactory { private static final Map> instances = new HashMap<>(); public static void register(String loggerMedium, Class extends LoggingOperation> instance) { if (loggerMedium != null && instance != null) { instances.put(loggerMedium, instance); } } public static LoggingOperation getInstance(ApplicationContext context, String loggerMedium) { if (instances.containsKey(loggerMedium)) { return context.getBean(instances.get(loggerMedium)); } return null; }}
getInstance
需要传入ApplicationContext
对象,这样就可以根据类型获取具体的实现了。
修改所有存储实现类,如下所示:
import org.springframework.stereotype.Component;@Componentclass RemoteServiceLog implements LoggingOperation { static { LoggerFactory.register("REMOTE", RemoteServiceLog.class); } public void log(String message) { // Implementation }}
总结
我们通过一个例子,不断迭代带大家理解了工厂模式,工厂模式是一种创建型设计模式,用于创建同一类型的不同实现对象。我们来总结下这种动态选择对象工厂模式的优缺点。
优点:
- 容易管理。在添加新的存储类时,只需将该类放入特定包中,在static代码块中注册它自己到工厂中。
- 松耦合,当您添加新的存储实现时,您不需要在工厂类中进行任何更改。
- 遵循SOLID编程原则。
缺点:
- 如果是用原生通过类加载的方式,代价比较大,因为它涉及 I/O 操作。但是如果使用的是SpringBoot,则无需担心,因为框架本身会调用组件。
- 需要额外编写一个
static
块,注册自己到工厂中,一不小心就遗漏了。
欢迎关注个人公众号【JAVA旭阳】交流沟通
关键词:
-
焦点简讯:K8S 性能优化 - K8S APIServer 调优
前言K8S性能优化系列文章,本文为第二篇:KubernetesAPIServer性能优化参数最佳实践。系列文章:《K8S性...
来源: -
【全球聚看点】Prompt-Engineering-Guide 学习摘要1
Prompt工程(Prompt-Engineering)是一种用于主动支持和协助工程团队进行及时决策的软件工具。它提供了...
来源: 工厂模式进阶用法,如何动态选择对象?
迷你天猫商城代码审计
焦点简讯:K8S 性能优化 - K8S APIServer 调优
【全球聚看点】Prompt-Engineering-Guide 学习摘要1
前端设计模式——装饰者模式
65寸4K大屏电视不到2000元 LCD白菜价即将结束:3月价格上涨10%
环球微头条丨最强AI再次进化 ChatGPT下周升级GPT-4:支持视频了
【环球聚看点】免费玩!《生化危机4:重制版》体验版上线:不限时、不限次
当前滚动:德国电动空中出租车Lilium jet完成测试:时速250km/h 全机36个电风扇
杀疯了!长安深夜放大招 购车百亿补贴:深蓝SL03直降2.2万
世界快看点丨分享几个常用的运维 shell 脚本
世界观点:佳兆业成今年首家复牌出险房企
全球球精选!一座河南小县城的全球钻石生意爆火:价格不到天然的1/3
我国再次成功发射一箭双星:天绘六号A/B星顺利进入预定轨道
当前快看:资助8年的女生毕业放弃工作 嫁给有钱人成家庭主妇 资助人:失眠好几天
天天通讯!上班族如何备考公务员_如何备考公务员
全球焦点!读Java性能权威指南(第2版)笔记12_堆内存中
怎么处理消息重发的问题?
每日热点:HEU KMS Activator 30.0.0全能系统数字许可激活工具(全新体验纪念版)
环球热议:用盆吃10袋泡面男子火了 回应月薪2万邀约:浇完家里18亩地再说
实时:第127篇:异步函数(async和await)练习题(异步,消息队列)
焦点!【LeetCode回溯算法#05】分割回文串(复习双指针判断回文以及substr函数使用记录)
今日热议:【django-vue】celery延迟任务、定时任务 django中使用celery 秒杀功能 双写一致性 首页轮播图定时更新 课程前端页面
世界热头条丨关于JAVA泛型数组类型擦除引发的问题及解决方案
环球今日讯!Mint安装MySQL
快讯:苹果iPhone 14黄色款预售:全新配色不加价 5999元起
天天微速讯:租客辞职要搬走被房东介绍工作 当事人感动
即时焦点:一辆腾势敢占两个充电桩:真不怕挨揍?
“常温超导”把A股都晃晕了!真要改变我们的生活?
《霍格沃茨之遗》渗人面部Bug:玩家看了起鸡皮疙瘩
全球短讯!美环保署署长:将在今年最终确定甲烷排放规则
21世纪20年代是几几年(21世纪20年代是几几年)
爆肝两万字,详解fastdfs分布式文件系统
初识rollup 打包、配置vue脚手架
世界热议:62.类模板
当前热文:C语言——可变参函数
陕西招聘会现3万月薪岗位学生排长龙 招聘人员:半天收简历150份
当前动态:供不应求!真我GT Neo5 1TB版真香:二手用户也抢着要
“山药成了精”?男子买到奇葩山药外形酷似人脚掌
每日视点!快速读懂Redis分布式锁的实现和原理
美少女三消游戏 《Mirror 2: Project X》开发组宣布破产解散
酒店回应到211大学招服务员:符合流程 面向所有高校毕业生
快看点丨京东CEO徐雷:百亿补贴效果超过预期、要做天天低价
【新要闻】比亚迪加入降价大军!宋Pro DM-i限时优惠:88元折扣6888元
焦点简讯:耳机煲机一般要煲多久_耳机煲机方法是什么?买回来新耳机要怎样煲?耳机要煲多久?
世界热点评!前端如何相对优雅管理api
全球速看:浙四医院官网招聘2021_浙四医院官网
环球观点:京东2022年收入超1万亿:“百亿补贴”会一直有!
简约时尚 健身备一件:361°新款轻薄速干衣39元冲量
越来越卖不动了!最畅销十款数码相机一览:索尼成赢家 第一性价比绝
全球通讯!Intel的大小核CPU架构:最终还是把一些老游戏坑了
往返近1000元!景区回应坐滑竿上山按斤收费:网友力挺明码标价
天天实时:【翻译】发布 .NET 8 Preview 1
前沿资讯!海洋风筝爆火 一周暴涨498%超过秋裤!网友:春天的信号
天天消息!甜香丝滑 旺旺邦德低脂轻乳咖啡官方狂促:合2元一瓶
《银河护卫队3》导演回击网友言论:确定选角不因其是黑人
世界热议:东风系引发车市价格大战!纯电宝马i3终端大促销:最大降幅超10万
每日热点:日本原药温和驱蚊配方:超威电热蚊香液3瓶1器14.9元发车
快讯:uni-popup 遮不住头部标题的解决办法
环球焦点!网友晒空荡新房 各大品牌疯狂随份子 开局一套房其他全靠送
快播:女子在地铁上脱鞋抠脚死皮掉一地 杭州地铁回应:列车到站会打扫
世界速递!为拯救者Y9000P 2023量身打造:联想推出新140W氮化镓适配器
每日看点!富士康否认清退临时工拆除流水线:运转正常
全球即时:美国一特斯拉撞上消防车造成一死一伤 现场惨烈!调查结果让人心痛
Envisics获得5000万美元C轮融资
当前滚动:(数据库系统概论|王珊)第十一章并发控制-第二、三、四节:封锁、封锁协议活锁和死锁
每日焦点!「中华田园敏捷开发」,是老板无能还是程序员无力?
【世界聚看点】探究SMC局部代码加密技术以及在CTF中的运用
记录--Vue自定义指令实现加载中效果v-load(不使用Vue.extend)
【焦点热闻】十分钟读懂火山引擎 DataLeap 数据治理实践
老头环壶头哥:击败女武神超4千次 期待DLC到来
天天资讯:儿子沉迷手机爸爸帮请假“逼”他连玩17小时:效果很好
焦点消息!供应iPhone 15的OLED屏幕漏光?国产面板一哥京东方回应:不予评价
当前关注:纵享丝滑 回味愉悦:德芙巧克力37.5元/斤(官价5折)
每日速看!成龙进组20天把半年的封闭用完了!新电影《龙马精神》4月上映:有吴京参演
每日视点!通过案例讲解python循环语句
【全球独家】基于应用理解的协议栈优化
关注:串口登录提示"Login incorrect"
热推荐:节能降耗 | AIRIOT智慧电力综合管理解决方案
实时:C++笔记--函数、预处理
【天天播资讯】为啥人一上车就爱睡觉?原来是被“催眠”了
全球观察:95后大厂女生裸辞开麻将馆当保洁:很享受自由和成就感
全球今日报丨2030年前后我国将实施火星采样返回:难度很大
【全球播资讯】特斯拉中国2月销量出炉 比亚迪能打5个特斯拉
航班晚点 山航机长提速帮乘客5分钟极限转机:提前20分钟到达
速递!首钢股份:2月重点产品产量同比均提升 预计国内钢材价格短期震荡偏强
焦点播报:Python常见面试题012. 可迭代对象和迭代器有啥区别?
从5分钟到60秒,袋鼠云数栈在热重启技术上的提效探索之路
世界观天下!我的脑内恋碍选项第二季会出吗_我的脑内恋碍选项第二季
【天天报资讯】ChatGPT火出圈!人工智能工程师平均招聘月薪突破2.5万
环球热文:三星推出冰淇淋主题键鼠套装:薄荷配色如此清凉
集成ChatGPT威力显著:微软Bing日活跃首次破亿
每日快报!3月罕见!郑州今日冲上30°C高温:成北方首个30℃+省会级城市
世界球精选!RTX 50显卡有望使用 GDDR7显存验证方案来了:狂飙36Gbps
无界生态发布会即将举办 焕新后的星纪魅族将带来哪些惊喜?
java代码审计-XSS
全球今日讯!vue中将base64流数据转成pdf文件可打印
环球速讯:1 MySql基础介绍
世界观焦点:英媒:多特蒙德中场贝林厄姆将决定自己的未来
观察:今年五一档已定档10部新片 王一博《长空之王》超65万人想看