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

“你这代码写的像坨屎”,今天我的代码又被当作典型被 CTO 骂了......于是他给我的建议如下:

责任链设计模式

模式定义

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

适用场景

适用于多节点的流程处理,每个节点完成各自负责的部分,节点之间不知道彼此的存在,比如 OA 的审批流,Java Web 开发中的 Filter 机制。

举一个生活中的例子,笔者之前租房的时候遇到了所谓的黑中介,租的时候感觉自己是上帝,但是坏了东西找他修的时候就像个孙子一样。

中介让我找门店客服,门店客服又让我找房东,房东又让我找她家老公,最终好说歹说才把这事了了(租房一定要找正规中介)。

实践经验

笔者目前所做的业务是校园团餐的聚合支付,业务流程很简单:

  • 学生打开手机付款码支付。

  • 食堂大妈使用机具扫付款码收款。

大学食堂有个背景是这样的,食堂有补贴,菜品比较便宜,所以学校是不愿意让社会人士去学校食堂消费的,鉴于此,我们在支付之前加了一套是否允许支付的检验逻辑。

大体如下:

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

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

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

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

  • 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,4 都差不多,但是第 3 步是不一样的,炒土豆丝得拿铲子翻炒,但是炒麻婆豆腐得拿勺子轻推,否则豆腐会烂(疫情宅在家,学了不少菜)。

使用场景

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

实践经验

还是接着之前语音播报的例子来说,后期我们新加了两个需求:

  • 消息推送需要增加 trace。

  • 有些通道推送失败需要重试。

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

  • trace 开始。

  • 通道开始推送。

  • 是否允许重试,如果允许执行重试逻辑。

  • 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 这一层做了收敛。

作者:踩刀诗人

出处:https://urlify.cn/J3mAna

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

我写的代码,又被CTO骂了......相关推荐

  1. 你写的代码和66万的车有什么区别

    看着他们写的代码,有时候不知道是骂还是不骂,只好说了他们一句,你看你写的代码和66万的奔驰车有什么区别,(他们蒙逼的状态好呆萌)还没运行就溢出了,能用吗,好好写你的代码,好好写你函数,我发现这段话真是 ...

  2. 阿里工程师年薪百万,写的代码到底咋样?

    点击"技术领导力"关注∆  每天早上8:30推送 来源| 淘系技术  作者|朱天富 导语:本文是由阿里技术专家,朱天富所写.介绍了阿里是如何提高代码质量的,读完此文你就会对大厂的代 ...

  3. 边看chromium的代码,边想骂人...

    这一年一直在看chromium for android的代码,边看边想骂,谷歌这帮人.. 一开始搞了个牛逼的架构,在安卓4.4上把以前android webkit团队的简单版替换掉了, 结果发现性能大 ...

  4. 程序员:为什么几个月后我自己写的代码也看不懂了?

    写在开始的"注释很重要","注释很重要","注释很重要",重要的事情说三遍. 程序员们大多都会有过这样的经历,就是要看别人写的代码.比如说公 ...

  5. python骂人代码大全_边看chromium的代码,边想骂人...

    这一年一直在看chromium for android的代码,边看边想骂,谷歌这帮人.. 一开始搞了个牛逼的架构,在安卓4.4上把以前android webkit团队的简单版替换掉了, 结果发现性能大 ...

  6. 年薪10万与年薪100万的程序员,写出代码的区别

    编程是一门创造性的工作,是一门艺术.我们每天与代码打交道,为什么普通码农辛苦一年只拿十万,而高级架构师年薪百万.最主要的就是我们敲出来的代码有差别,差别在意大部分码农敲出来坏的代码,而高级架构师能敲出 ...

  7. OSChina 周三乱弹 —— 不是你写的代码,修什么BUG

    2019独角兽企业重金招聘Python工程师标准>>> 各位 OSCer 大家壕,今天是星期三!上班没商量的日子. @动弹办主任:编码一天下来,为什么这么累啊, @不是小白:日写代码 ...

  8. Python写的代码打包成.exe可执行文件

    Python写的代码打包成.exe可执行文件 1. 安装pyinstaller 2. [在线生成icon](http://www.ico51.cn/) 3. 打包命令 pyinstaller -i x ...

  9. 转程序员,都去写一写前端代码吧

    转自: http://www.oschina.net/news/36972/programmer-write-frond-end-code 你可以认为我是一个极端的人,就像有许多人专注于自己的领域而不 ...

最新文章

  1. node中使用es6/7/8 --- 支持性与性能
  2. ThreadPool.QueueUserWorkItem的用法
  3. PTA 栈 (20分)(全网首发)(实现一个栈Stack,要求实现Push(出栈)、Pop(入栈)、Min(返回最小值的操作)的时间复杂度为O(1))
  4. chart控件做实时曲线显示_Python 如何实时绘制数据
  5. 探索 .Net Core 的 SourceLink
  6. 深入理解张正友相机标定法:数学理论详细推导
  7. flowable 图片缓存
  8. java通过匹配合并数据(数据预处理)
  9. oracle数据库赋权_oracle数据库删除赋权
  10. python基础之五大标准数据类型
  11. 4.二叉搜索树转为有序双向链表(递归算法与非递归算法)
  12. CSS基本选择器之类选择器多类名(CSS、HTML)
  13. SpringAOP 学习笔记
  14. 03月11日单应矩阵与鸟瞰图IPM变换
  15. webStorm 修改 JavaScript 版本为 ES6
  16. java贪吃蛇柚子功能_关于java贪吃蛇心得
  17. 麦子学院深度学习进阶课程题目纲要
  18. 基于FreeFEM++的有限元编程--2
  19. 两台局域网内的阿里云服务器传文件
  20. pmp十大知识领域,49个过程的4W1H

热门文章

  1. linux c sql server 存储过程,SQL Server 2016 - 本机编译的函数
  2. 关于学习Python的一点学习总结(28->收集参数及分配参数)
  3. 牛妹吃豆子(二维前缀和模板,修改+求和)
  4. 牛客练习赛61 E 相似的子串(二分+哈希)难度⭐⭐⭐
  5. 面试题 16.19.水域大小
  6. 落谷 P1060 开心的金明
  7. poj3680(最小费用流 + 拆点)
  8. android的天气和时钟部件,Android的天气和时钟部件
  9. 独立云计算服务商的多维实践之道:用户需求驱动变革
  10. 怎样熟练使用一项技术