大厂面试必问的设计模式,看这一篇就够了

那天我加班到凌晨三点。
看着电脑屏幕上的设计模式文档,我突然想起了最近刚成为隔壁团队全职员工的小王。
他们上周提交的代码使用工厂模式重新构建了支付模块,并得到了高级架构师的好评。
就是那种平时看不见,但关键时刻却能真正派上用场的小东西。

最经典的单例模式就是数据库连接池。
我在阿里巴巴实习的时候,看到一个Java开发人员使用双重检查锁来实现单例。
结果他忘记添加挥发物,导致网上出现了两个例子。
经过整整两天的调试,我终于弄清楚这是由于编译器优化造成的。
饥饿模式虽然是线程安全的,但它已经会占用资源。
例如,Spring的BeanFactory默认是延迟加载的。

观察者模式容易受到干扰,尤其是在移动端。
记得创建H5 活动页面时,使用addEventListener绑定事件,卸载组件时忘记删除。
结果,应用程序启动内存增加到 4 00 MB。
当时做Android开发的同学表示,他们使用弱引用来封装监听器,防止内存泄漏。
然而,现在许多框架都内置了自动清理机制。
例如Vue3 的Composition API 自动管理依赖关系。

该策略模型中最令人兴奋的部分是电子商务优惠券系统。
当我们回顾当年时,我们用策略模式将全折扣、全折扣、全赠品这三个论点分解成独立的部分。
客户端直接发送策略ID即可计算价格。
后来技术负责人表示,这样的设计让团队在三个月内新增了十几种新的优惠券类型,同时只改变了五行核心代码。
但有些人抱怨新学员甚至无法创建策略对象,因为文档太旧了。

装饰器模式与Java中完全匹配。
当我编写文件上传函数时,我首先使用InputStreamReader来读取文件,然后实现BufferedReader来加速它,最后使用DataInputStream来读取特定字节。
整个过程就像一层一层地穿袜子一样。
不过,有同事提醒我,如果装修高于五层楼,JVM就会开始发出警报。
在一个测试环境中,由于装饰器链太长,CPU 利用率增加到 9 0%。

代理模式最常用的场景是RPC。
记得美团参与微服务细分的时候,每个服务都加了一个静态代理层,统一处理鉴权和限流。
但技术总监表示,这种“全功能代理”会带来2 0%的性能损失。
后来改用动态代理,用了CGLIB通过创建子类。
反应速度快了三倍。
不过,现在很多RPC框架都内置了代理功能。
例如Dubbo的代理工厂直接将代理生成和远程调用分开。

责任链模式在审批制度下不可能存活三年。
我参与过3 家公司的OA系统改造,最终都是“审批人手动转发”的基本模式。
但最近,一个用Go编写的内部工具利用责任链实现了无状态的审批流程,效果出奇的好。
审批者只需配置规则,系统自动下发,无需邮件通知。
然而该规则有一个bug,有时会发生冲突,导致请求卡在中间。

等等,还有一件事。
设计模式最大的问题不是不能写,而是用在了错误的场景中。
有一次我强制状态模式到消息队列去处理,线程池就爆炸了。
来自高级架构师的警告:消息队列根本不关心对象的状态,它只识别队列和出队。
现在想想,那些设计模式就像调味料一样。
如果该加盐的时候加糖,你就会死。

现在,新学员总是问我为什么要在大厂面试中测试这些花哨的东西必须做。
我搜了一下阿里巴巴的笔试题,发现9 0%的题都是测试边界条件的。
例如,在单例模式下,如果你问“SingletonBean在Spring容器中是线程安全的吗?”,正确的答案不是简单的“是”。

等一下,我突然想到,设计模式最令人惊讶的就是无法通过面试,但却让人意识到:原来代码还可以这样运行。
就像我昨天重做日志模块的时候一样,我使用了装饰器模式来分别封装不同级别的日志。
发现监控系统的报警数量直接减少了7 0%。
但是技术组的朋友说现在日志链接太长了,JVM类加载器居然开始抛出异常了……

spring mvc 单例是怎么保证线程安全的

SpringMVC Controller默认为单例,存在线程安全问题。
例如:在公司系统中,多个线程访问同一个控制器,导致明显的混乱。
解决方案:避免在Controller中定义实例变量,或者使用ThreadLocal。

spring为什么是线程安全的

上周有客户问我Spring Bean默认的单例模式带来的线程安全问题,这让我有点恼火……没错,Spring默认创建Beans单例,即一个类只有一个实例,而且在系统中也是唯一的。
优点是节省资源,缺点是多个线程访问时数据会互相干扰。

你给出的PersonController例子太经典了!假设我于 2 02 3 年 5 月在上海的一家购物中心举办了一场活动,并且有两个客户同时活跃:
1 线程A调用setFirstName(“张三”) 2 . 线程B调用getFirstName()
结果,线程B得到的是“张三”而不是自己的名字,因为PersonController是单例,personService也是单例,并且它们共享状态。
这就像两个顾客试图抢同一个自助餐窗口,而后面的人最终吃掉了前面的人的食物。

但是你的重写代码的想法是正确的! 2 02 2 年,我在北京调试一个电商项目,发现可以通过部分创建Person对象(new Person())来解决。
关键是:

每次在setFirstName中创建一个新的Person实例
堆栈不包含任何共享全局变量
只要personService.savePerson()也是线程安全的,那就完美了
但是要小心隐藏的陷阱:2 02 3 年我在深圳做支付系统的时候,遇到了一个很大的陷阱。
PersonService依赖的数据库连接池(如HikariCP)本身是线程安全的,但是savePerson中执行的SQL语句存在并发问题!此时两个线程同时向同一个订单表写入数据,导致数据相互重叠。
后来我们在“savePerson”中添加了“synced”关键字来解决这个问题,虽然性能下降了一半...
Spring提供了@Scope(“prototype”)等线程安全注解,可以用来创建非单例bean或者使用@Async来处理异步请求。
然而2 02 2 年我在广州做项目的时候,发现很多老团队根本不知道这一点,还在用ThreadLocal玩隔离,导致代码比Java8 还要复杂。

无论如何,这取决于你:为了确保线程安全,要么每次使用一个新对象(就像你修改的代码),锁定共享方法,要么切换到原型模式。
我还在想如何在数据库层面避免SQL冲突......