行是知之始,知是行之成。这篇文章主要讲述为什么你的设计模式用的就是比别人垃圾?相关的知识,希望能为你提供帮助。
??
??
前言经常看到一些设计模式的文章,写了很多内容,也举了一些很“生动形象”的例子。
但是可能和《Head First 设计模式》会有一样的问题:看完了,我会了,但是好像用不上?或者是硬套设计模式。
举几个我见过的极端的例子:
俩字段,也要来个 Builder
3个 if,提个策略模式
5行代码还很简单的初始化,也要弄个 Factory
……
至于为什么会出现这种问题……我聊聊我的看法
原因大多数的研发人员,做的工作都是业务功能开发,也就是常说的 CRUD。只是不同的业务场景,CRUD的复杂度不同而已。
可是对于业务代码来说,很多情况下不太好套用设计模式,或者说没法很好的应用设计模式。
平时看到的最多的是策略模式的文章吧,为什么呢?
我猜是因为这个最好写,应用在业务代码里比较简单;随便一个稍微复杂点的场景,就可以套用一下策略模式,把多个 if 拆分到多个类里。
的确,业务代码里适当的使用策略模式可以降低复杂度;但就算用也得住一个度,不要把各种业务里的 if 都换成策略模式了,不然代码会炸的……
之前见过一个项目,虽然是内部xx系统,但那个研发小哥可能是走火入魔了。抽了 80 多个策略类出来,这八十多个类里又分为十几个组,每组七八个策略类,但每个类里的代码,也不过十几二十行,而且还有重复代码。
我当时问了他一句:你在养蛊吗?
像这个研发小哥就是一个反例,滥用设计模式,过分的将各种分支代码全部套进设计模式了。不过我猜想他可能是为了学习吧,学以致用……
其他的委托代理状态之类的模式,想应用在业务代码里,就比较费劲了,因为没有那么多合适的场景。但策略模式则不同,有 if 的地方都可以尝试套一下……
可是设计模式是用来解决问题,降低/转移复杂度的,而不是增加复杂度。
非业务代码里的设计模式跳出业务代码来,甚至说跳出纯业务代码来之后,想应用设计模式就比较简单了,甚至不需要你硬套,遇到问题时就自然的会想到用设计模式来解决。
举个栗子
系统里一般需要一个 traceId/requestId 来将整个链路串起来,配合日志打印或者集中式的APM抽取。
就拿单体应用来说,一般用日志框架的 MDC 来绑定这个 traceId。在 Filter 或者 一些 AOP 里,给 MDC 一个 traceID,那么整个调用链路都可以用这一个 ID,打印日志时就可以根据 traceId 区分不同请求了,就像这样:
2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - 请求第0步
2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - 请求第1步
2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - 请求第2步
2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - 请求第0步
2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - 请求第1步
2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - 请求第2步
…
通过 000/111 这个 traceId 就可以区分是哪个请求。
可 MDC 是通过 ThreadLocal 进行存储数据的,ThreadLocal 毕竟是和线程绑定的。如果链路中使用了线程池处理,那可怎么办?线程池里子线程打印日志的时候,MDC 可获取不到主线程的 traceId,但对于这个请求来说,主子线程都是一个链路……
还记得这句话吗?
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决”
这里借助委托模式,来增加一个中间层,问题就很好解决了。
既然是主子线程的数据传递问题,那么只需要在创建子线程的时候,从主线程里将 MDC 里的 traceId 拿出来,传递给新建的子线程就可以了,就像这样:
public class MDCDelegateRunnable implements Runnable
private Runnable target;
private String traceId;
public MDCDelegateRunnable(Runnable target, String traceId)
this.target = target;
this.traceId = traceId;
@Override
public void run()
MDC.put("traceId",traceId);
target.run();
MDC.remove("traceId");
然后再来一个委托模式的线程池,将 execute方法重写。把线程池中原本的 Runnable 对象包装为刚才的 MDCDelegateRunnable,在创建时,将 traceId 通过构造参数传递
public class MDCDelegateExecutorService extends AbstractExecutorService
public MDCDelegateExecutorService(AbstractExecutorService target)
this.target = target;
private AbstractExecutorService target;
@Override
public void shutdown()
target.shutdown();
//...
@Override
public void execute(@NotNull Runnable command)
target.execute(new MDCDelegateRunnable(command, MDC.get("traceId")));
搞定,来测试一下:
public static void main(String[] args) throws IOException, ExecutionException, InterruptedException
MDC.put("traceId","111");
new MDCDelegateExecutorService((AbstractExecutorService) Executors.newFixedThreadPool(5)).execute(new Runnable()
@Override
public void run()
System.out.println("runnable: "+MDC.get("traceId"));
);
Future< String> future = new MDCDelegateExecutorService((AbstractExecutorService) Executors.newFixedThreadPool(5)).submit(new Callable< String> ()
@Override
public String call() throws Exception
return MDC.get("traceId");
);
System.out.println("callable: "+future.get());
System.in.read();
//output
runnable: 111
callable: 111
完美,本来麻烦的 traceId 传递问题,现在通过一个简单的委托模式就解决了。不用修改调用方代码,也没有破坏线程池的代码。
JDK 里的委托模式还记得Executors#newSingleThreadExecutor这个单线程线程池的创建方法吧,那什么情况下需要单线程的线程池呢?
比如我只是需要一个异步并且获取返回的操作,直接 new 线程 start 的话,获取返回值又不太方便,如果通过线程池的 Callable/Runnable + Future 就方便了:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future< String> future = executorService.submit(new Callable< String> ()
@Override
public String call() throws Exception
// do sth...
return data;
);
String data = https://www.songbingjia.com/android/future.get();
executorService.shutdown();
对于单线程异步的场景来说,甚至都不需要维护一个单例的线程池,每次 new/shutdown 也可以。可是我都单线程了,每次还要 shutdown 是不是有点不太方便,万一哪里忘了 shutdown 了,那可不完蛋了……
【为什么你的设计模式用的就是比别人垃圾()】 JDK 的设计者也想到了这个问题,而且他们也已经解决了这个问题。和上面的例子类似,利用一个简单的委托模式,就可以完美解决这个问题:
public static ExecutorService newSingleThreadExecutor()
//创建 FinalizableDelegatedExecutorService 委托类
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue< Runnable> ()));
// 委托类里,在 finalize 被委托的线程池对象的 shutdown方法,自动关闭线程池
static class FinalizableDelegatedExecutorService
extends DelegatedExecutorService
FinalizableDelegatedExecutorService(ExecutorService executor)
super(executor);
protected void finalize()
推荐阅读
- 我在工地抬杠,抬到了我梦寐以求的996!TvT~
- Java编程练习题,基础不牢地动山摇!看看着50道你会几道!
- Java从入门到放弃 · ArrayList集合小练习
- 重中之重!!面对JVM时,栈帧之局部变量表的重要性不用我来说吧!
- 老生常谈NIO,我们再来聊一聊关于NIO的故事和一些用法!
- 现在后端都在用什么数据库存储数据(以及数据库的架构!)
- DTMO直播预告丨ChunJun 2022年开源规划&支持异构数据源
- ls命令设置颜色
- Spring Cloud 入门 -- 搭建Eureka注册中心 实现服务者与消费者的服务调用