点击上方蓝色“方志朋”,选择“设为星标”

回复“666”获取独家整理的学习资料!

来源:https://www.cnblogs.com/chopper-poet/p/12626768.html

大多数时候我都是写一些业务代码,可能一堆CRUD就能解决问题,但是这样的工作对技术人的提升并不多,如何让自己从业务中解脱出来找到写代码的乐趣呢,我做过一些尝试,使用设计模式改善自己的业务代码就是其中的一种。

设计模式实践

责任链设计模式

责任链设计模式定义

请求在一个链条上处理,链条上的受理者处理完毕之后决定是继续往后传递还是中断当前处理流程。

适用场景

适用于多节点的流程处理,每个节点完成各自负责的部分,节点之间不知道彼此的存在,比如OA的审批流,java web开发中的Filter机制。举一个生活中的例子,笔者之前租房的时候遇到了所谓的黑中介,租的时候感觉自己是上帝,但是坏了东西找他修的时候就像个孙子一样,中介让我找门店客服,门店客服又让我找房东,房东又让我找她家老公,最终好说歹说才把这事了了(租房一定要找正规中介)。

实践经验

笔者目前所做的业务是校园团餐的聚合支付,业务流程很简单,1.学生打开手机付款码支付,2.食堂大妈使用机具扫付款码收款。大学食堂有个背景是这样的,食堂有补贴,菜品比较便宜,所以学校是不愿意让社会人士去学校食堂消费的,鉴于此,我们在支付之前加了一套是否允许支付的检验逻辑,大体如下:

1.某档口只允许某类用户用户消费,比如教师档口只允许教师消费,学生档口不允许校外用户消费;

2.某个档口一天只允许某类用户消费几次,比如教师食堂一天只允许学生消费一次;

3.是否允许非清真学生消费,比如某些清真餐厅,是不允许非清真学生消费的;

针对这几类情况我建立了三类过滤器,分别是:

SpecificCardUserConsumeLimitFilter:按用户类型判断是否允许消费

DayConsumeTimesConsumeLimitFilter:按日消费次数判断是否允许消费

MuslimConsumeLimitFilter:非清真用户是否允许消费

判断逻辑是先通过SpecificCardUserConsumeLimitFilter判断当前用户是否可以在此档口消费,如果允许继续由DayConsumeTimesConsumeLimitFilter判断当天消费次数是否已用完,如果未用完继续由MuslimConsumeLimitFilter判断当前用户是否满足清真餐厅的就餐条件,前面三条判断,只要有一个不满足就提前返回。

部分代码如下:

public boolean canConsume(String uid,String shopId,String supplierId){//获取用户信息,用户信息包含类型(student:学生,teacher:老师,unknown:未知用户)、名族(han:汉族,mg:蒙古族)UserInfo userInfo = getUserInfo(uid);//获取消费限制信息,限制信息包含是否允许非清真消费、每种类型的用户是否允许消费以及允许消费的次数ConsumeConfigInfo consumeConfigInfo = getConsumeConfigInfo(shopId,supplierId) // 构造消费限制过滤器链条ConsumeLimitFilterChain filterChain = new ConsumeLimitFilterChain();filterChain.addFilter(new SpecificCardUserConsumeLimitFilter());filterChain.addFilter(new DayConsumeTimesConsumeLimitFilter());filterChain.addFilter(new MuslimConsumeLimitFilter());boolean checkResult = filterChain.doFilter(filterChain, schoolMemberInfo, consumeConfigInfo);//filterChain.doFilter方法public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,ConsumeConfigInfo consumeConfigInfo ){//迭代调用过滤器if(index<filters.size()){return filters.get(index++).doFilter(filterChain, userInfo, consumeConfigInfo);}}//SpecificCardUserConsumeLimitFilter.doFilter方法public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,ConsumeConfigInfo consumeConfigInfo ){//获取某一类型的消费限制,比如student允许消费,unknown不允许消费CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);// 判断当前卡用户是否允许消费if (consumeCardConfig != null) {if ((!CAN_PAY.equals(cardConsumeConfig .getEnabledPay()))) {return false;}}//其余情况,继续往后传递return filterChain.doFilter(filterChain, memberInfo, consumeConfig);}//DayConsumeTimesConsumeLimitFilter.doFilter方法public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,ConsumeConfigInfo consumeConfigInfo ){//获取某一类型的消费限制,比如student可以消费2次CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);//获取当前用户今天的消费次数int consumeCnt = getConsumeCnt(userInfo)  if(consumeCnt >= cardConsumeConfig.getDayConsumeTimesLimit()){return false;}//其余情况,继续往后传递return filterChain.doFilter(filterChain, memberInfo, consumeConfig);}

总结

将每种限制条件的判断逻辑封装到了具体的Filter中,如果某种限制条件的逻辑有修改不会影响其他条件,如果需要新加限制条件只需要重新构造一个Filter织入到FilterChain上即可。

策略设计模式

策略设计模式定义

定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换

适用场景

主要是为了消除大量的if else代码,将每种判断背后的算法逻辑提取到具体的策略对象中,当算法逻辑修改时对使用者无感知,只需要修改策略对象内部逻辑即可。这类策略对象一般都实现了某个共同的接口,可以达到互换的目的。

实践经验

笔者之前有个需求是用户扫码支付以后向档口的收银设备推送一条支付消息,收银设备收到消息以后会进行语音播报,逻辑很简单,就是调用推送平台推送一条消息给设备即可,但是由于历史原因,某些设备对接的推送平台是不一样的,A类设备优先使用信鸽推送,如果失败了需要降级到长轮询机制,B类设备直接使用自研的推送平台即可,还有个现状是A类和B类的消息格式是不一样的(不同的团队开发,后期被整合到一起),鉴于此,我抽象出PushStrategy接口,其具体的实现有IotPushStrategy和XingePushStrategy,分别对应自研推送平台的推送策略和信鸽平台的推送策略,使用者时针对不同的设备类型使用不同的推送策略即可。部分代码如下:

/*** 推送策略* /
public interface PushStrategy {/**@param deviceVO设备对象,包扣设备sn,信鸽pushid@param content,推送内容,一般为json*/public CallResult push(AppDeviceVO deviceVO, Object content);
}IotPushStrategy implements PushStrategy{/**@param deviceVO设备对象,包扣设备sn,信鸽pushid@param content,推送内容,一般为json*/public CallResult push(AppDeviceVO deviceVO, Object content){//创建自研推送平台需要的推送报文Message message = createPushMsg(deviceVO,content);//调用推送平台推送接口IotMessageService.pushMsg(message);}
}XingePushStrategy implements PushStrategy{/**@param deviceVO设备对象,包扣设备sn,信鸽pushid@param content,推送内容,一般为json*/public CallResult push(AppDeviceVO deviceVO, Object content){//创建信鸽平台需要的推送报文JSONObject jsonObject = createPushMsg(content);//调用推送平台推送接口if(!XinggePush.pushMsg(message)){//降级到长轮询...}}
}/**
消息推送Service
*/
MessagePushService{pushMsg(AppDeviceVO deviceVO, Object content){if(A设备){XingePushStrategy.push(deviceVO,content);} else if(B设备){IotPushStrategy.push(deviceVO,content);}}
}

总结

将每种通道的推送逻辑封装到了具体的策略中,某种策略的变更不会影响其他策略,由于实现了共同接口,所以策略可以互相替换,对使用者友好,比如java ThreadPoolExecutor中的任务拒绝策略,当线程池已经饱和的时候会执行拒绝策略,具体的拒绝逻辑被封装到了RejectedExecutionHandler的rejectedExecution中。

模板设计模式

模板设计模式定义

模板的价值就在于骨架的定义,骨架内部将问题处理的流程已经定义好,通用的处理逻辑一般由父类实现,个性化的处理逻辑由子类实现。比如炒土豆丝和炒麻婆豆腐,大体逻辑都是1.切菜,2.放油,3.炒菜,4.出锅,1,2,4都差不多,但是第3步是不一样的,炒土豆丝得拿铲子翻炒,但是炒麻婆豆腐得拿勺子轻推,否则豆腐会烂(疫情宅在家,学了不少菜)。

使用场景

不同场景的处理流程,部分逻辑是通用的,可以放到父类中作为通用实现,部分逻辑是个性化的,需要子类去个性实现。

实践经验

还是接着之前语音播报的例子来说,后期我们新加了两个需求:1.消息推送需要增加trace

2.有些通道推送失败需要重试

所以现在的流程变成了这样:

1.trace开始

2.通道开始推送

3.是否允许重试,如果允许执行重试逻辑

4.trace结束

其中1和4是通用的,2和3是个性化的,鉴于此我在具体的推送策略之前增加了一层父类的策略,将通用逻辑放到了父类中,修改后的代码如下:

abstract class AbstractPushStrategy implements PushStrategy{@Overridepublic CallResult push(AppDeviceVO deviceVO, Object content) {//1.构造spanSpan span = buildSpan();//2.具体通道推送逻辑由子类实现CallResult callResult = doPush(deviceVO, content);//3.是否允许重试逻辑由子类实现,如果允许执行重试逻辑if(!callResult.isSuccess() && canRetry()){doPush(deviceVO, content);}//4.trace结束span.finish() }//具体推送逻辑由子类实现protected abstract CallResult doPush(AppDeviceVO deviceDO, Object content) ;//是否允许重试由子类实现,有些通道之前没有做消息排重,所有不能重试protected abstract boolean canRetry(CallResult callResult);}XingePushStrategy extends AbstractPushStrategy{@Overrideprotected CallResult doPush(AppDeviceVO deviceDO, Object content) {//执行推送逻辑}@Overrideprotected boolean canRetry(CallResult callResult){return false}
}

总结

通过模板定义了流程,将通用逻辑放在父类实现,减少了重复代码,个性化逻辑由子类自己实现,子类间修改代码互不干扰也不会破坏流程。

观察者设计模式

观察者设计模式定义

顾名思义,此模式需要有观察者(Observer)和被观察者(Observable)两类角色,当Observable状态变化时会通知Observer,Observer一般会实现一类通用的接口,比如java.util.Observer,Observable需要通知Observer时,逐个调用Observer的update方法即可,Observer的处理成功与否不应该影响Observable的流程。

使用场景

一个对象(Observable)状态改变需要通知其他对象,Observer的存在不影响Observable的处理结果,Observer的增删对Observable无感知,比如kafka的消息订阅,producer发送一条消息到topic,至于是1个还是10个consumer订阅这个topic,producer是不需要关注的。

实践经验

在责任链设计模式那块我通过三个Filter解决了消费限制检验的问题,其中有一个Filter是用来检验消费次数的,我这里只是读取用户的消费次数,那么消费次数的累加是怎么完成的呢?其实累加这块就用到了观察者模式,具体来讲是这样,当交易系统收到支付成功回调时会通过Spring的事件机制发布“支付成功事件”,这样负责累加消费次数和负责语音播报的订阅者就会收到“支付成功事件”,进而做各自的业务逻辑,画个简单的图描述一下:


代码结构大体如下:

/**
支付回调处理者
*/
PayCallBackController implements ApplicationContextAware {private ApplicationContext applicationContext;//如果想获取applicationContext需要实现ApplicationContextAware接口,Spring容器会回调setApplicationContext方法将applicationContext注入进来@Overridepublic void setApplicationContext(ApplicationContext applicationContext)throws BeansException {this.applicationContext = applicationContext;}@RequestMapping(value = "/pay/callback.do")public View callback(HttpServletRequest request){if(paySuccess(request){//构造支付成功事件PaySuccessEvent event = buildPaySuccessEvent(...);//通过applicationContext发布事件,从而达到通知观察者的目的this.applicationContext.publishEvent(event);} }
}
/*** 语音播报处理者**/
public class VoiceBroadcastHandler implements ApplicationListener<PaySuccessEvent>{@Overridepublic void onApplicationEvent(PaySuccessEvent event) {//语音播报逻辑}
}//其他处理者的逻辑类似

总结

观察者模式将被观察者和观察者之间做了解耦,观察者存在与否不会影响被观察者的现有逻辑。

装饰器设计模式

装饰器设计模式定义

装饰器用来包装原有的类,在对使用者透明的情况下做功能的增强,比如java中的BufferedInputStream可以对其包装的InputStream做增强,从而提供缓冲功能。

使用场景

希望对原有类的功能做增强,但又不希望增加过多子类时,可以使用装饰器模式来达到同样的效果。

实践经验

笔者之前在推动整个公司接入trace体系,因此也提供了一些工具来解决trace的自动织入和上下文的自动传递,为了支持线程间的上下文传递,我增加了TraceRunnableWrapper这个装饰类,从而起到将父线程的上下文透传到子线程中,对使用者完全透明,代码如下:

/**
可以自动携带trace上下文的Runnable装饰器
*/
public class TraceRunnableWrapper implements Runnable{//被包装的目标对象private Runnable task;private Span parentSpan = null;public TraceRunnableWrapper(Runnable task) {//1.获取当前线程的上下文(因为new的时候还没有发生线程切换,所以需要在这里将上下文获取)//对这块代码感兴趣的可以查看opentracing APIio.opentracing.Scope currentScope = GlobalTracer.get().scopeManager().active();//2.保存父上下文parentSpan = currentScope.span();this.task = task;}@Overridepublic void run() {//run的时候将父线程的上下文绑定到当前线程io.opentracing.Scope scope = GlobalTracer.get().scopeManager().activate(parentSpan,false);task.run();}
}//使用者
new Thread(new Runnable(){run(...)}).start()替换为new TraceRunnableWrapper(new Runnable(){run(...)}).start()

总结

使用装饰器模式做了功能的增强,对使用者来说只需要做简单的组合就能继续使用原功能。

外观设计模式

外观设计模式定义

何为外观,就是对外提供一个统一的入口,一是可以影藏系统内部的细节,二是可以降低使用者的复杂度,比如SpringMvc中的DispaterServlet,所有的Controller都是通过DispaterServlet统一暴露。

使用场景

降低使用者的复杂度,简化客户端的接入成本。

实践经验

笔者所在的公司对外提供了一些开放能力给第三方ISV,比如设备管控、统一支付、对账单下载等能力,由于分属于不同的团队,所以对外提供的接口形式各异,初期还好,接口不多,ISV也能接受,但是后期接口多了ISV就开始抱怨接入成本太高,为了解决这一问题,我们在开放接口前面加了一层前端控制器GatewayController,其实就是我们后来开放平台的雏形,GatewayController对外统一暴露一个接口gateway.do,将对外接口的请求参数和响应参数统一在GatewayController做收敛,GatewayController往后端服务路由时也采用统一接口,改造前后对比如下图:


大概代码如下:

使用者:
HttpClient.doPost("/gateway.do","{'method':'trade.create','sign':'wxxaaa','timestamp':'15311111111'},'bizContent':'业务参数'")GatewayController:
@RequestMapping("/gateway.do")
JSON gateway(HttpServletRequest req){//1.组装开放请求OpenRequest openRequest = buildOpenRequest(req);OpenResponse openResponse = null;//2.请求路由if("trade.create".equals(openRequest.getMethod()){//proxy to trade service by dubboopenResponse = TradeFacade.execute(genericParam);} else if("iot.message.push".equals(openRequest.getMethod()){//proxy to iot service by httpclientopenResponse = HttpClient.doPost('http://iot.service/generic/execute'genericParam);}if(openResponse.isSuccess()){return {"code":"10000","bizContent":openResponse.getResult()};}else{return {"code":"20000","bizCode":openResponse.getCode()};}}

总结:

采用外观模式屏蔽了系统内部的一些细节,降低了使用者的接入成本,就拿GatewayController来说,ISV的鉴权,接口的验签等重复工作统一由它实现,ISV对接不同的接口只需要关心一套接口协议接口,由GatewayController这一层做了收敛。

热门内容:
  • 七个开源的 SpringBoot 前后端分离项目,Star过千,快去收藏夹吃灰吧!

  • 道友自诉:入职中软一个月(外包华为)就离职了!

  • 腾讯推出高性能 RPC 开发框架

  • 再见了SpringMVC,这个框架有点厉害,甚至干掉了Servlet!

  • 原来 Elasticsearch 还可以这么理解

  • 干掉Navicat:正版,MySQL官方客户端真香!

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)

设计模式在工作中的实践相关推荐

  1. 设计模式在我工作中的巧妙实践

    设计模式在我工作中的实践 大多数时候我都是写一些业务代码,可能一堆 CRUD 就能解决问题,但是这样的工作对技术人的提升并不多,如何让自己从业务中解脱出来找到写代码的乐趣呢,我做过一些尝试,使用设计模 ...

  2. 实战!工作中常用到哪些设计模式

    前言 平时我们写代码呢,多数情况都是流水线式写代码,基本就可以实现业务逻辑了.如何在写代码中找到乐趣呢,我觉得,最好的方式就是:使用设计模式优化自己的业务代码.今天跟大家聊聊日常工作中,我都使用过哪些 ...

  3. 工作中常用的 6 种设计模式!

    前言 哈喽,大家好. 平时我们写代码呢,多数情况都是流水线式写代码,基本就可以实现业务逻辑了.如何在写代码中找到乐趣呢,我觉得,最好的方式就是:使用设计模式优化自己的业务代码.今天跟大家聊聊日常工作中 ...

  4. 大家一起来说一说自己掌握的设计模式和在工作中的应用场景,分享经验

    做了比较多的通讯和SDK,虽然是用C++,但是SDK对外的都是C的接口,而且项目中主要是消息封装,socket IO模型和多线程, 消息收发,业务比较少,所以用的OOP也比较少,用了很多年C++,却一 ...

  5. 收藏!工作中Git使用实践和常用命令流程合集

    来自:匠心Java 工作中git是一项必不可少的技能,在项目的开发进程中起着至关重要的作用 下面介绍一些git在工作中的一些使用实践.常用流程.常用命令,供大家参考! 一:前言 Git的定义是:分布式 ...

  6. 设计模式在外卖营销业务中的实践

    来自:美团技术团队 业务策略多变导致需求多变,是业界很多技术团队面临的最具挑战的问题之一.那么如何设计一套易于扩展和维护的营销系统呢? 今天的文章来自美团外卖营销技术团队,他们分享了从领域模型到代码工 ...

  7. [vue] 实际工作中,你总结的vue最佳实践有哪些?

    [vue] 实际工作中,你总结的vue最佳实践有哪些? .babelrc 是目前 babel-polyfill 的最佳实践 { "presets": [ [ "@babe ...

  8. git add 所有修改文件_Git 技术干货!工作中quot;Gitquot;的使用实践和常用命令合集!

    作者:洋仔聊编程 出自:InfoQ 写作平台 原文:xie.infoq.cn/article/1ce91dc60431b1b8845729d41 工作中git是一项必不可少的技能,在项目的开发进程中起 ...

  9. 设计模式在美团外卖营销业务中的实践

    一.前言 随着美团外卖业务的不断迭代与发展,外卖用户数量也在高速地增长.在这个过程中,外卖营销发挥了"中流砥柱"的作用,因为用户的快速增长离不开高效的营销策略.而由于市场环境和业务 ...

最新文章

  1. oracle 查看表是否被锁
  2. 关于各组评价的自我评价
  3. neo4j python 算法_python操作neo4j简单实例
  4. LeetCode 1274. 矩形内船只的数目(分治)
  5. 安卓的短信记录导入苹果_安卓换苹果手机如何进行便签数据转移?
  6. ASP.NET Core 3.0 发布
  7. python标签打印工具_NiceLabel Designer 2017条码标签打印软件
  8. NF、$NF、FR、FNR举例说明
  9. SQL 基础面试题(四)
  10. excel通过转成xml格式模板,下载成excel文件
  11. LINUX加载静态库so,取得函数地址并调用
  12. mysql 查询表,视图,触发器,函数,存储过程
  13. 函数式编程?别费力气了,它就是个愚蠢的玩具
  14. Linux用户和进程管理
  15. 浏览器网络异常导致JS文件请求失败的问题及处理
  16. linux vad检测,VAD树结构体的属性以及遍历
  17. Bluedroid 打开蓝牙流程
  18. 通过ip能查服务器型号,通过ip地址查服务器归属
  19. VCN中文编程平台的优势(二)——产品的价格
  20. Go语言panic详解中

热门文章

  1. Spring Boot 2.0 常见问题总结(一)
  2. ubuntu14.04初体会
  3. Android Q 变更和新特性
  4. 0409-0416的笔记
  5. python复习冒泡排序
  6. WPF入门教程-转载
  7. 多线程实现生产者消费者模型
  8. Python ATM
  9. Mysql 查询 字符串 (索引和通配符)
  10. MySql存储引擎特性对比