从《设计模式》红宝书问世那天起,Java程序员就把23种模式供上了神坛。但你们有没有想过,这些模式本质上都是给Java擦屁股的产物?今天我们不谈模式有多伟大,只撕开代码表面,看看那些被模式掩盖的语言伤疤。
单例模式:Java的精神分裂症
所有语言都有全局状态需求,但正常语言都大大方方提供全局变量/全局函数。唯独Java这个怪胎,既不给全局变量权限,又拒绝全局函数的存在。于是程序员们不得不发明单例模式——用private构造+static变量+getInstance()三件套,硬生生造出个全局变量的缝合怪。
深入JDK源码,你会发现最著名的单例案例是Runtime类——这个承载JVM生命周期的核心类,getRuntime()方法内部用synchronized保证线程安全。但讽刺的是,当Java程序员在Spring框架里使用@Bean注解时,容器管理的单例对象其实就是在打自己的脸:说好的不要全局变量呢?
看看隔壁C语言怎么做的:直接extern声明全局变量;Python更干脆,模块级变量天然全局;Go直接包级别变量走起。只有Java程序员要跪着写单例,还要在代码评审时被质问"为什么不用枚举单例"——这他妈不就是五十步笑百步?
更恶劣的影响在架构层面蔓延。这个本应用于全局配置的模式,最后变成依赖注入框架的毒瘤。Spring的@Bean注解本质就是官方认证的全局变量,但非要套个"控制反转"的套子才敢见人。Spring的ApplicationContext成为了事实上的全局变量仓库,Hibernate的SessionFactory必须用单例维持连接池,这些框架本质上都在用更复杂的方式实现C语言一行代码就能完成的事情。
观察者模式:回调地狱的遮羞布
事件驱动编程的核心就是回调函数,但Java偏要把简单问题复杂化。在Python里,一个函数参数就能解决的问题,到了Java必须搞出Observer接口、Observable类、update()方法这一整套仪式。
打开JDK内置的Observable源码,你会看到用Vector存储观察者对象——这种上古遗毒设计就是为弥补Java缺乏闭包机制。JavaScript用匿名函数三行代码搞定的事情,Java必须用三个类文件才能实现。
RxJava的崛起更是黑色幽默。这个号称响应式编程的库,本质上是在用观察者模式处理异步流。当Java程序员骄傲地展示Observable.interval(1, TimeUnit.SECONDS).subscribe()时,JavaScript开发者早在十年前就用setInterval(()=>{},1000)解决了同样的问题。
最可笑的是Java8引入的lambda表达式。本应简化回调的利器,却因为Java强制要求函数式接口必须显式声明,导致每次使用lambda都得先定义@FunctionalInterface。这种脱裤子放屁的设计,让本可简洁的代码再次陷入类型声明的泥潭。
其他擦屁股行为一览
工厂模式:
- 因Java禁止返回抽象类型(直到Java14的密封类),必须用Factory类绕路
- 构造器不能返回子类实例(对比Go语言的接口即装即用)
- 类型系统强制要求显式声明(对比Python的鸭子类型)
装饰器模式:
InputStream套BufferedInputStream再套GZIPInputStream的经典套娃,本质是Java禁止修改final类的历史遗留问题。看看Ruby的模块混入(mixin)机制,装饰功能直接注入原有类,何需层层包装?
适配器模式:
当Java接口新增方法时,所有实现类必须同步修改,这才催生出适配器模式这种补丁方案。反观Go语言的接口隐式实现机制,完全规避了这个问题。
模板方法模式:
强制子类继承抽象类才能复用算法骨架,而Python通过高阶函数实现同样功能,代码量减少60%。
框架如何把轮椅焊死在身上?
Spring框架用XML配置声明Bean的过程,本质是把工厂模式机械化,@Autowired注解成为了新时代的全局变量注射器。我们不得不思考:绕了二十年,最后不还是回到了全局变量的原点?
Hibernate的EntityManagerFactory是单例模式的超级变种,MyBatis的SqlSessionFactory更是把工厂模式玩出花。但这些框架的存在,恰恰证明Java语言在基础功能上的缺失——如果语言层面支持全局变量,还需要这些庞然大物吗?
最讽刺的是Lombok这个补丁神器。@Singleton注解自动生成单例代码,@Builder注解实现建造者模式——这些本应存在于语言层面的特性,竟然要依靠注解处理器来曲线救国。
设计模式的瘟疫传播
当设计模式瘟疫蔓延到C++社区,画面就变得格外荒诞。这个拥有运算符重载、模板元编程、RAII等大杀器的语言,本应是设计模式的掘墓人,却有一群信徒硬要把Java的轮椅往自己身上套。
最典型的悲剧发生在单例模式。C++明明有命名空间级静态变量,却偏要学Java搞Meyer's Singleton:
1class Singleton {
2public:
3 static Singleton& getInstance() {
4 static Singleton instance;
5 return instance;
6 }
7private:
8 Singleton() = default;
9};
这本质上是用栈对象模拟Java的堆单例,既违背RAII原则,又可能遭遇static初始化顺序陷阱。真正优雅的C++做法应该是直接声明全局变量配合匿名命名空间,但模式教徒们总觉得不加个getInstance()就不够"专业"。
工厂模式在C++社区更是水土不服。当Java程序员苦于不能返回抽象类型时,C++的模板元编程早就给出了终极方案:
1template<typename T>
2T* create() { return new T(); }
这种泛型工厂根本不需要继承体系,但总有人非要在C++里复刻Java的AbstractFactory模式,造出层层继承的类金字塔。更讽刺的是,这些模式信徒往往一边写着工厂类,一边在代码里用裸new操作符创建对象。
观察者模式在C++里的实施堪称行为艺术。现代C++本可以通过std::function+lambda实现优雅回调:
1button.on_click([](auto& event) { /*处理逻辑*/ });
但模式原教旨主义者非要定义Observer基类,让所有监听器强制继承,完美复刻Java的糟粕。当Qt框架用信号槽机制革新事件处理时,这群人还在手动维护观察者列表,用dynamic_cast检查对象类型。
最黑色幽默的是设计模式对C++模板的恐惧症。当Java程序员在羡慕C#的泛型时,C++模式教徒却在拒绝使用模板特性——因为他们心中"模式圣经"里没有写过模板的用法。于是我们能看到有人用策略模式实现类型分发,而完全忽略模板特化的存在;有人用访问者模式遍历对象树,却不知道C++17的std::variant+std::visit组合拳。
这些C++程序员就像拿着瑞士军刀却只用来开啤酒瓶盖的莽夫,他们膜拜设计模式的姿势,比Java原教旨主义者更可笑。整个社区都在推崇Modern C++的简洁哲学,这些模式木乃伊还在用1998年的编程思维,在21世纪给C++代码打补丁。
设计模式的真正价值
我反对的不是模式本身,而是把模式当圣经的教徒思维。Kotlin用object声明单例,C#用event关键字实现观察者,当Rust用trait替代适配器——这些现代语言都在证明:好的语言设计应该消灭模式,而不是制造模式。
下次当你们跪拜设计模式时,不妨想想这个问题:为什么C程序员从不需要单例模式?为什么Python程序员很少谈观察者模式?因为这些语言不需要用模式来掩盖语言缺陷!
Java诞生28年了,该从设计模式的迷宫里醒来了。要么拥抱现代语言特性,要么继续在模式屎山里缝缝补补——这是每个Java程序员迟早要做的选择。
如需讨论,欢迎到知乎文章页面发表评论。