说实话,我大学时候背《设计模式》背得头晕,那个单例模式的双重锁检查,我考试都背错了。工作以后前几年,我特别迷信模式,遇到个什么需求,先想该套哪个模式,感觉这样才专业。后来有个老同事跟我说,你这不是设计,是查字典。我当时还不服气,直到我参与了一个老项目的重构,才意识到我踩的坑全是自己按模式造出来的。
那个项目是个支付模块,代码里用了一大堆工厂模式。每个支付通道是一个子类,然后工厂负责实例化。刚开始挺带劲,后来新增通道越来越麻烦,每加一个就要改工厂,还得加一堆if/else。整个工厂类膨胀到几百行,看代码的人直摇头。后来我实在忍不住,把工厂拆了,改成在配置里注册,用反射加载,再往下我用函数式接口加Map,把创建对象的逻辑都塞进一个构造器里。最后那部分代码从三百多行变成六十行,测试也简单多了。这让我想起书里的策略模式和模板方法模式,其实在Python里用一阶函数就能搞定,在Java里也能用lambda,完全没必要搞一堆类继承。
单例模式也很有意思。Java里写一个线程安全的懒汉单例,要volatile、双重检查、私有构造器,还要防反射攻击。结果Spring容器里的Bean不是单例吗?其实那只是容器管理的单例作用域,跟那本书里的模式完全是两回事。我见过有人为了测试单例,搞了一堆mock静态方法,最后干脆改成构造器注入。模式本身不是问题,问题是模式被当成了目标,而不是解决手段。23个模式里,我估摸着一半以上在现代语言里都有更简洁的替代方案。
我的观点可能比较极端:设计模式更像是那个时代的补丁。当年C++和Java没有闭包,没有元编程,所以需要一大堆类结构来模拟函数传递和动态行为。现在很多语言原生支持函数作为对象,比如Go的函数一等公民,比如Ruby的鸭子类型,比如Python的装饰器。你去套旧模式,反而让代码更绕。我现在的原则是:如果有个模式能直接解决问题,但写起来要绕好几层抽象,那就说明语言可能有更好的写法。设计模式背后的那些原则——封装变化、面向接口而不是实现、组合优于继承——永不过时。但具体到什么Adapter、Facade、Decorator这些类结构,真的可以按需取用,别当祖宗供着。最后说一句,我重写那两套项目以后,最深的体会是:先用最简单的方式写出来,等真的需要抽象再抽象,比预先套模式靠谱得多。