大家好,我是程序羊。 平时我们写代码呢,多数情况都是流水线式写代码,基本就可以实现业务逻辑了。那如何在写代码中找到乐趣呢,我觉得一个有效的方式就是:尝试使用设计模式来优化自己的业务代码,以提高质量! 所以今天我们就结合具体业务场景,来梳理一下日常工作中,高频使用的几种设计模式,相信对大家平时的工作会很有帮助。 文章很长,大家也可以收藏备用。工作中常用到哪些设计模式 1.策略模式 1.1 业务场景 假设有这样的业务场景,大数据系统把文件推送过来,根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码:
if(type==a){ //按照a格式解析 }else if(type==b){ //按b格式解析}else{ //按照默认格式解析} 这个代码可能会存在哪些问题呢?
如果分支变多,这里的代码就会变得臃肿,难以维护,可读性低。 如果你需要接入一种新的解析类型,那只能在原有代码上修改。 说得专业一点的话,就是以上代码,违背了面向对象编程的开闭原则以及单一原则。
开闭原则(对于扩展是开放的,但是对于修改是封闭的):增加或者删除某个逻辑,都需要修改到原来代码 单一原则(规定一个类应该只有一个发生变化的原因):修改任何类型的分支逻辑代码,都需要改动当前类的代码。 如果你的代码就是酱紫:有多个if...else等条件分支,并且每个条件分支,可以封装起来替换的,我们就可以使用策略模式来优化。
1.2 策略模式定义 策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的的客户。这个策略模式的定义是不是有点抽象呢?那我们来看点通俗易懂的比喻:
假设你跟不同性格类型的小姐姐约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去逛街买买买最合适。当然,目的都是为了得到小姐姐的芳心,请看电影、吃小吃、逛街就是不同的策略。
策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
1.3 策略模式使用 策略模式怎么使用呢?酱紫实现的:
一个接口或者抽象类,里面两个方法(一个方法匹配类型,一个可替换的逻辑实现方法) 不同策略的差异化实现(就是说,不同策略的实现类) 使用策略模式 1.3.1 一个接口,两个方法 public interface ifilestrategy { //属于哪种文件解析类型 filetyperesolveenum gainfiletype(); //封装的公用算法(具体的解析方法) void resolve(object objectparam);} 1.3.2 不同策略的差异化实现 a 类型策略具体实现
@componentpublic class afileresolve implements ifilestrategy { @override public filetyperesolveenum gainfiletype() { return filetyperesolveenum.file_a_resolve; } @override public void resolve(object objectparam) { logger.info(a 类型解析文件,参数:{},objectparam); //a类型解析具体逻辑 }} b 类型策略具体实现
@componentpublic class bfileresolve implements ifilestrategy { @override public filetyperesolveenum gainfiletype() { return filetyperesolveenum.file_b_resolve; } @override public void resolve(object objectparam) { logger.info(b 类型解析文件,参数:{},objectparam); //b类型解析具体逻辑 }} 默认类型策略具体实现
@componentpublic class defaultfileresolve implements ifilestrategy { @override public filetyperesolveenum gainfiletype() { return filetyperesolveenum.file_default_resolve; } @override public void resolve(object objectparam) { logger.info(默认类型解析文件,参数:{},objectparam); //默认类型解析具体逻辑 }} 1.3.3 使用策略模式 如何使用呢?我们借助spring的生命周期,使用applicationcontextaware接口,把对用的策略,初始化到map里面。然后对外提供resolvefile方法即可。
@componentpublic class strategyuseservice implements applicationcontextaware{ private map ifilestrategymap = new concurrenthashmap(); public void resolvefile(filetyperesolveenum filetyperesolveenum, object objectparam) { ifilestrategy ifilestrategy = ifilestrategymap.get(filetyperesolveenum); if (ifilestrategy != null) { ifilestrategy.resolve(objectparam); } } //把不同策略放到map @override public void setapplicationcontext(applicationcontext applicationcontext) throws beansexception { map tmepmap = applicationcontext.getbeansoftype(ifilestrategy.class); tmepmap.values().foreach(strategyservice -> ifilestrategymap.put(strategyservice.gainfiletype(), strategyservice)); }} 2. 责任链模式 2.1 业务场景 我们来看一个常见的业务场景,下订单。下订单接口,基本的逻辑,一般有参数非空校验、安全校验、黑名单校验、规则拦截等等。很多伙伴会使用异常来实现:
public class order { public void checknullparam(object param){ //参数非空校验 throw new runtimeexception(); } public void checksecurity(){ //安全校验 throw new runtimeexception(); } public void checkbacklist(){ //黑名单校验 throw new runtimeexception(); } public void checkrule(){ //规则拦截 throw new runtimeexception(); } public static void main(string[] args) { order order= new order(); try{ order.checknullparam(); order.checksecurity (); order.checkbacklist(); order2.checkrule(); system.out.println(order success); }catch (runtimeexception e){ system.out.println(order fail); } }} 这段代码使用了异常来做逻辑条件判断,如果后续逻辑越来越复杂的话,会出现一些问题:如异常只能返回异常信息,不能返回更多的字段,这时候需要自定义异常类。
并且,阿里开发手册规定:禁止用异常做逻辑判断。
【强制】 异常不要用来做流程控制,条件控制。说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。
如何优化这段代码呢?可以考虑责任链模式
2.2 责任链模式定义 当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式。
责任链模式为请求创建了一个接收者对象的链。执行链上有多个对象节点,每个对象节点都有机会(条件匹配)处理请求事务,如果某个对象节点处理完了,就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
责任链模式实际上是一种处理请求的模式,它让多个处理器(对象节点)都有机会处理该请求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递:
责任链模式 打个比喻:
假设你晚上去上选修课,为了可以走点走,坐到了最后一排。来到教室,发现前面坐了好几个漂亮的小姐姐,于是你找张纸条,写上:“你好, 可以做我的女朋友吗?如果不愿意请向前传”。纸条就一个接一个的传上去了,后来传到第一排的那个妹子手上,她把纸条交给老师,听说老师40多岁未婚...
2.3 责任链模式使用 责任链模式怎么使用呢?
一个接口或者抽象类 每个对象差异化处理 对象链(数组)初始化(连起来) 2.3.1 一个接口或者抽象类 这个接口或者抽象类,需要:
有一个指向责任下一个对象的属性 一个设置下一个对象的set方法 给子类对象差异化实现的方法(如以下代码的dofilter方法) public abstract class abstracthandler { //责任链中的下一个对象 private abstracthandler nexthandler; /** * 责任链的下一个对象 */ public void setnexthandler(abstracthandler nexthandler){ this.nexthandler = nexthandler; } /** * 具体参数拦截逻辑,给子类去实现 */ public void filter(request request, response response) { dofilter(request, response); if (getnexthandler() != null) { getnexthandler().filter(request, response); } } public abstracthandler getnexthandler() { return nexthandler; } abstract void dofilter(request filterrequest, response response);} 2.3.2 每个对象差异化处理 责任链上,每个对象的差异化处理,如本小节的业务场景,就有参数校验对象、安全校验对象、黑名单校验对象、规则拦截对象
/** * 参数校验对象 **/@component@order(1) //顺序排第1,最先校验public class checkparamfilterobject extends abstracthandler { @override public void dofilter(request request, response response) { system.out.println(非空参数检查); }}/** * 安全校验对象 */@component@order(2) //校验顺序排第2public class checksecurityfilterobject extends abstracthandler { @override public void dofilter(request request, response response) { //invoke security check system.out.println(安全调用校验); }}/** * 黑名单校验对象 */@component@order(3) //校验顺序排第3public class checkblackfilterobject extends abstracthandler { @override public void dofilter(request request, response response) { //invoke black list check system.out.println(校验黑名单); }}/** * 规则拦截对象 */@component@order(4) //校验顺序排第4public class checkrulefilterobject extends abstracthandler { @override public void dofilter(request request, response response) { //check rule system.out.println(check rule); }} 2.3.3 对象链连起来(初始化)&& 使用 @component(chainpatterndemo)public class chainpatterndemo { //自动注入各个责任链的对象 @autowired private list abstracthandlelist; private abstracthandler abstracthandler; //spring注入后自动执行,责任链的对象连接起来 @postconstruct public void initializechainfilter(){ for(int i = 0;i ifilestrategymap.put(strategyservice.gainfiletype(), strategyservice)); }} 5.2 使用工厂模式 定义工厂模式也是比较简单的:
一个工厂接口,提供一个创建不同对象的方法。 其子类实现工厂接口,构造不同对象 使用工厂模式 5.3.1 一个工厂接口 interface ifileresolvefactory{ void resolve();} 5.3.2 不同子类实现工厂接口 class afileresolve implements ifileresolvefactory{ void resolve(){ system.out.println(文件a类型解析); }}class bfileresolve implements ifileresolvefactory{ void resolve(){ system.out.println(文件b类型解析); }}class defaultfileresolve implements ifileresolvefactory{ void resolve(){ system.out.println(默认文件类型解析); }} 5.3.3 使用工厂模式 //构造不同的工厂对象ifileresolvefactory fileresolvefactory;if(filetype=“a”){ fileresolvefactory = new afileresolve();}else if(filetype=“b”){ fileresolvefactory = new bfileresolve(); }else{ fileresolvefactory = new defaultfileresolve();}fileresolvefactory.resolve(); 一般情况下,对于工厂模式,你不会看到以上的代码。工厂模式会跟配合其他设计模式如策略模式一起出现的。
6. 单例模式 6.1 业务场景 单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。i/o与数据库的连接,一般就用单例模式实现de的。windows里面的task manager(任务管理器)也是很典型的单例模式。
来看一个单例模式的例子
public class lanhansingleton { private static lanhansingleton instance; private lanhansingleton(){ } public static lanhansingleton getinstance(){ if (instance == null) { instance = new lanhansingleton(); } return instance; }} 以上的例子,就是懒汉式的单例实现。实例在需要用到的时候,才去创建,就比较懒。如果有则返回,没有则新建,需要加下 synchronized关键字,要不然可能存在线性安全问题。
6.2 单例模式的经典写法 其实单例模式还有有好几种实现方式,如饿汉模式,双重校验锁,静态内部类,枚举等实现方式。
6.2.1 饿汉模式 public class ehansingleton { private static ehansingleton instance = new ehansingleton(); private ehansingleton(){ } public static ehansingleton getinstance() { return instance; } } 饿汉模式,它比较饥饿、比较勤奋,实例在初始化的时候就已经建好了,不管你后面有没有用到,都先新建好实例再说。这个就没有线程安全的问题,但是呢,浪费内存空间呀。
6.2.2 双重校验锁 public class doublechecksingleton { private volatile static doublechecksingleton instance; private doublechecksingleton() { } public static doublechecksingleton getinstance(){ if (instance == null) { synchronized (doublechecksingleton.class) { if (instance == null) { instance = new doublechecksingleton(); } } } return instance; }} 双重校验锁实现的单例模式,综合了懒汉式和饿汉式两者的优缺点。以上代码例子中,在synchronized关键字内外都加了一层 if条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
6.2.3 静态内部类 public class innerclasssingleton { private static class innerclasssingletonholder{ private static final innerclasssingleton instance = new innerclasssingleton(); } private innerclasssingleton(){} public static final innerclasssingleton getinstance(){ return innerclasssingletonholder.instance; }} 静态内部类的实现方式,效果有点类似双重校验锁。但这种方式只适用于静态域场景,双重校验锁方式可在实例域需要延迟初始化时使用。
6.2.4 枚举 public enum singletonenum { instance; public singletonenum getinstance(){ return instance; }} 枚举实现的单例,代码简洁清晰。并且它还自动支持序列化机制,绝对防止多次实例化。
好了,以上就是今天的内容分享,感谢大家的收看,我们下期见。
京瓷荣登CDP“应对气候变化最高评级A名单” GE签署独家协议
多光子显微镜成像技术:用于体内神经元成像的多种技术
浙江联通携手中兴通讯成功完成了5G新型智能城域网试点
联想发布哪两大智能场景解决方案?
高压跌落式熔断器结构
高频使用的几种设计模式
专家面对面研讨会合肥站-孟昊:问题解答和产品的现场调试(2)
工控项目调试全过程分享值得收藏
北斗卫星和低轨通信卫星技术对比
这几种智能门禁系统方案,一点你就明白
关于AI低功耗设计的新方法
西华大学选购我司HS-DR-5导热系数测试仪
【教程】串口服务器接入物联网平台实现IO控制
未来或许用DNA技术来进行数据存储
基于晶闸管控制的台灯电路图
2021年全球智能手表:苹果独占三成市场,操作系统竞争再升级
气体激光器种类东方闪光告诉您
华为Mate10什么时候上市最新消息:华为P10“身败名裂”,华为Mate10正在火速赶来救场的路上
滤波器的基本原理与功能!
影响焊锡膏贴片加工中焊点光亮的因素有哪些?