49. 设计模式常见误区与反模式:过度设计,模式滥用,错误实现
设计模式这东西,用好了是利器,用不好就是灾难。我见过太多团队,一上来就套一堆模式,结果代码比不用模式还难维护。今天咱们就聊聊,那些常见的坑到底在哪。
误区一:过度设计——为未来买单,但未来没来
说白了,过度设计就是「想太多」。你还没确定需求,就开始抽象接口、搞工厂、上策略模式。结果呢?三个月后需求变了,你那些精心设计的抽象层全得重写。
我个人习惯是:先写能跑的代码,再考虑要不要重构。别一上来就想着「万一以后要扩展怎么办」。你想想看,大部分「万一」根本不会发生。
避坑指南: 我曾经接手过一个项目,代码里用了三层抽象工厂加依赖注入,结果业务逻辑只有两个分支。改一个字段要改五个文件。这就是典型的「为未来过度投资」。
怎么判断是不是过度设计?问自己三个问题:
- 这个抽象层现在有超过两个实现吗?
- 未来三个月内,这个接口真的会变吗?
- 去掉这个模式,代码会变差多少?
如果答案都是「否」,那你很可能在过度设计。
误区二:模式滥用——手里有锤子,看啥都是钉子
我面试过不少人,简历上写着「精通23种设计模式」。一问怎么用的,回答永远是「单例、工厂、观察者」。嗯,这三个模式确实好用,但你不能所有问题都往上套啊。
举个例子:有个同事为了解耦,把两个类的简单调用改成了观察者模式。结果呢?代码从两行变成了五十行,调试时还得追踪事件流。你说这图啥?
核心原则: 模式是工具,不是目标。如果不用模式也能清晰解决问题,那就别用。
常见的滥用场景:
| 模式 | 滥用表现 | 正确用法 |
|---|---|---|
| 单例 | 所有全局状态都塞进单例 | 只用于真正需要唯一实例的资源(如日志、配置) |
| 工厂 | 每个类都配一个工厂 | 只在对象创建逻辑复杂或需要切换实现时使用 |
| 观察者 | 简单回调也改成事件总线 | 用于一对多、解耦的场景,且事件数量可控 |
| 策略 | 两个分支也搞策略模式 | 策略数量≥3,且可能动态切换时使用 |
误区三:错误实现——模式用对了,但写错了
这个坑最常见。很多人照着书抄代码,但没理解精髓。我举几个例子:
1. 单例模式:线程安全没处理好
// 错误实现:双重检查锁,但指令重排导致问题
class Singleton {
private:
static Singleton* instance;
public:
static Singleton* getInstance() {
if (!instance) {
lock_guard<mutex> lock(mtx);
if (!instance) {
instance = new Singleton(); // 可能指令重排!
}
}
return instance;
}
};
// 正确实现:使用 call_once 或局部静态变量
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 保证线程安全
return instance;
}
};
嗯,这里要注意:C++11 之后,局部静态变量的初始化是线程安全的。所以别再手写双重检查锁了。
2. 工厂模式:switch-case 满天飞
我见过有人用工厂模式,结果工厂里写了一堆 switch-case。每次加新产品都要改工厂类,这跟不用模式有啥区别?
// 反模式:工厂里写 switch
Product* Factory::create(const string& type) {
if (type == "A") return new ProductA();
else if (type == "B") return new ProductB();
// 每次加产品都要改这里
}
// 正确做法:注册机制
class Factory {
map<string, function<Product*()>> creators;
public:
void register(const string& type, function<Product*()> creator) {
creators[type] = creator;
}
Product* create(const string& type) {
return creators[type]();
}
};
3. 观察者模式:忘记处理生命周期
这个坑我踩过。观察者模式里,如果被观察者已经销毁,但观察者还在引用它,那就等着崩溃吧。
我的建议: 使用弱引用(weak_ptr)来管理观察者列表。或者在被观察者析构时,主动通知所有观察者解除绑定。
如何避免这些坑?
我总结了几条经验:
- 先写测试,再考虑模式。 测试能帮你发现「是不是真的需要这个模式」。
- 代码评审时,多问「为什么用这个模式」。 如果回答是「大家都这么用」,那就要警惕了。
- 保持简单。 能用函数解决的问题,别用类;能用类解决的问题,别用模式。
- 多读源码。 看看优秀的开源项目是怎么用模式的。你会发现,他们用得很少,但用得很精。
最后说一句: 设计模式是经验的总结,不是教条。如果你觉得某个模式让代码变复杂了,那就别用。你的直觉往往比书本更靠谱。
一句话总结: 设计模式是帮你解决问题的,不是制造问题的。如果用了模式反而让代码更难懂、更难改,那一定是哪里搞错了。