你在公司,写的代码像坨*,你咋不用它 !!!
共 9860字,需浏览 20分钟
·
2020-09-16 06:49
大多数时候我都是写一些业务代码,可能一堆CRUD就能解决问题,但是这样的工作对技术人的提升并不多,如何让自己从业务中解脱出来找到写代码的乐趣呢,我做过一些尝试,使用设计模式改善自己的业务代码就是其中的一种。
责任链设计模式
定义
请求在一个链条上处理,链条上的受理者处理完毕之后决定是继续往后传递还是中断当前处理流程。
适用场景
适用于多节点的流程处理,每个节点完成各自负责的部分,节点之间不知道彼此的存在,比如OA的审批流,java web开发中的Filter机制。举一个生活中的例子,笔者之前租房的时候遇到了所谓的黑中介,租的时候感觉自己是上帝,但是坏了东西找他修的时候就像个孙子一样,中介让我找门店客服,门店客服又让我找房东,房东又让我找她家老公,最终好说歹说才把这事了了(租房一定要找正规中介)。
实践经验
笔者目前所做的业务是校园团餐的聚合支付,业务流程很简单,1.学生打开手机付款码支付,2.食堂大妈使用机具扫付款码收款。大学食堂有个背景是这样的,食堂有补贴,菜品比较便宜,所以学校是不愿意让社会人士去学校食堂消费的,鉴于此,我们在支付之前加了一套是否允许支付的检验逻辑,大体如下:
1.某档口只允许某类用户用户消费,比如教师档口只允许教师消费,学生档口不允许校外用户消费;
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 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上即可。
策略设计模式
定义
定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换
适用场景
实践经验
/**
* 推送策略
* /
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);
}
}
}
总结
模板设计模式
定义
使用场景
不同场景的处理流程,部分逻辑是通用的,可以放到父类中作为通用实现,部分逻辑是个性化的,需要子类去个性实现。
实践经验
还是接着之前语音播报的例子来说,后期我们新加了两个需求:
1.消息推送需要增加trace
2.有些通道推送失败需要重试 所以现在的流程变成了这样:
1.trace开始
2.通道开始推送
3.是否允许重试,如果允许执行重试逻辑
4.trace结束 其中1和4是通用的,2和3是个性化的,鉴于此我在具体的推送策略之前增加了一层父类的策略,将通用逻辑放到了父类中,修改后的代码如下:
abstract class AbstractPushStrategy implements PushStrategy{
@Override
public CallResult push(AppDeviceVO deviceVO, Object content) {
//1.构造span
Span 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{
@Override
protected CallResult doPush(AppDeviceVO deviceDO, Object content) {
//执行推送逻辑
}
@Override
protected boolean canRetry(CallResult callResult){
return false
}
}
总结
通过模板定义了流程,将通用逻辑放在父类实现,减少了重复代码,个性化逻辑由子类自己实现,子类间修改代码互不干扰也不会破坏流程。
观察者设计模式
模式定义
使用场景
实践经验
代码结构大体如下:
/**
支付回调处理者
*/
PayCallBackController implements ApplicationContextAware {
private ApplicationContext applicationContext;
//如果想获取applicationContext需要实现ApplicationContextAware接口,Spring容器会回调setApplicationContext方法将applicationContext注入进来
@Override
public 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>{
@Override
public void onApplicationEvent(PaySuccessEvent event) {
//语音播报逻辑
}
}
//其他处理者的逻辑类似
总结
观察者模式将被观察者和观察者之间做了解耦,观察者存在与否不会影响被观察者的现有逻辑。
装饰器设计模式
定义
装饰器用来包装原有的类,在对使用者透明的情况下做功能的增强,比如java中的BufferedInputStream可以对其包装的InputStream做增强,从而提供缓冲功能。
使用场景
希望对原有类的功能做增强,但又不希望增加过多子类时,可以使用装饰器模式来达到同样的效果。
实践经验
/**
可以自动携带trace上下文的Runnable装饰器
*/
public class TraceRunnableWrapper implements Runnable{
//被包装的目标对象
private Runnable task;
private Span parentSpan = null;
public TraceRunnableWrapper(Runnable task) {
//1.获取当前线程的上下文(因为new的时候还没有发生线程切换,所以需要在这里将上下文获取)
//对这块代码感兴趣的可以查看opentracing API
io.opentracing.Scope currentScope = GlobalTracer.get().scopeManager().active();
//2.保存父上下文
parentSpan = currentScope.span();
this.task = task;
}
@Override
public 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()
总结
使用装饰器模式做了功能的增强,对使用者来说只需要做简单的组合就能继续使用原功能。
外观设计模式
定义
使用场景
降低使用者的复杂度,简化客户端的接入成本。
实践经验
大概代码如下:
使用者:
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 dubbo
openResponse = TradeFacade.execute(genericParam);
} else if("iot.message.push".equals(openRequest.getMethod()){
//proxy to iot service by httpclient
openResponse = 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这一层做了收敛。
END
免费领取 1000+ 道面试资料!!小编这里有一份面试宝典《Java 核心知识点.pdf》,覆盖了 JVM,锁、高并发、Spring原理、微服务、数据库、Zookeep人、数据结构等等知识点,包含 Java 后端知识点 1000+ 个,部分如下: