《设计模式》是GoF(Gang of Four,四人帮)的经典著作,也是每个程序员的必读书。
这本书出版于1994年,到现在已经快三十年了。书中介绍的23种设计模式,至今仍是面向对象设计的基础。很多人说,设计模式已经过时了,现在是函数式编程、微服务、云原生的时代。但我觉得,设计模式的思想,永远不会过时。
我最近重读了这本书,有了很多新的体会。第一次读的时候,我只记住了模式的名字和UML图;这次重读,我开始理解模式背后的设计原则和思想。
本文分享我的读后感,包括设计模式的本质、常用模式的理解、对软件设计的思考、设计模式的局限,以及为什么三十年后这本书依然值得读。
一、这本书是什么
先简单介绍一下这本书。
1. 作者和背景
《设计模式》的作者是四位程序员:Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides,人称GoF(四人帮)。
这本书的全名是《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)。
90年代初,面向对象编程开始流行,但很多人写的面向对象代码,其实是"用面向对象语言写的面向过程代码"。GoF总结了前人的经验,把常见的面向对象设计技巧整理成了23种模式,帮助大家写出更好的面向对象代码。
2. 23种设计模式
书中介绍了23种设计模式,分为三类:
创建型模式(5种):
- 工厂方法(Factory Method)
- 抽象工厂(Abstract Factory)
- 建造者(Builder)
- 原型(Prototype)
- 单例(Singleton)
结构型模式(7种):
- 适配器(Adapter)
- 桥接(Bridge)
- 组合(Composite)
- 装饰(Decorator)
- 外观(Facade)
- 享元(Flyweight)
- 代理(Proxy)
行为型模式(11种):
- 责任链(Chain of Responsibility)
- 命令(Command)
- 解释器(Interpreter)
- 迭代器(Iterator)
- 中介者(Mediator)
- 备忘录(Memento)
- 观察者(Observer)
- 状态(State)
- 策略(Strategy)
- 模板方法(Template Method)
- 访问者(Visitor)
3. 模式的要素
每个模式都包含四个要素:
- 模式名称:一个简短的名字,方便交流
- 问题:描述了什么时候使用模式
- 解决方案:描述了设计的组成部分、它们之间的关系
- 效果:描述了模式的优缺点和适用场景
这四个要素,构成了一个完整的模式描述。
二、设计模式的本质
重读之后,我对设计模式的本质有了新的理解。
1. 设计模式不是代码模板
很多人以为,设计模式就是代码模板,遇到问题就套一个模式,把代码写出来。
这是对设计模式的误解。设计模式不是代码模板,而是"解决问题的思路"。
同一个模式,在不同的语言、不同的场景下,代码实现可能完全不同。但核心的思路是一样的:如何组织代码,如何分配职责,如何应对变化。
2. 设计模式是"经验的总结"
设计模式,本质上是前人经验的总结。
GoF把那些反复出现的问题和解决方案,整理成了模式。这些模式,不是他们发明的,而是他们从大量的代码实践中总结出来的。
所以,学习设计模式,不是学习"新东西",而是学习"前人已经验证过的好方法"。
3. 设计模式的核心是"应对变化"
设计模式的核心目标,是应对变化。
软件需求总是在变。今天要加一个功能,明天要改一个逻辑。如果代码写得不好,每次变化都要改很多地方,很容易出bug。
设计模式,就是教我们如何组织代码,让代码更容易应对变化。通过封装、抽象、解耦,让变化的影响范围最小化。
4. 设计模式遵循的原则
设计模式背后,是几个面向对象的设计原则:
- 单一职责原则:一个类只做一件事
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:子类可以替换父类
- 接口隔离原则:接口要小而专
- 依赖倒置原则:依赖抽象,不依赖具体
这五个原则,是SOLID原则。设计模式,就是这些原则的具体应用。
三、我最有体会的几个模式
说说我最有体会的几个模式。
1. 策略模式(Strategy)
策略模式是我用得最多的模式之一。
策略模式的核心是:定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换。
比如,一个电商系统,支付方式有微信支付、支付宝、银行卡。如果用if-else写,每加一种支付方式都要改代码。用策略模式,每种支付方式是一个策略类,新增支付方式只需要加一个类,不需要改原有代码。
# 策略模式示例
class PaymentStrategy:
def pay(self, amount):
pass
class WeChatPay(PaymentStrategy):
def pay(self, amount):
print(f"微信支付{amount}元")
class AliPay(PaymentStrategy):
def pay(self, amount):
print(f"支付宝支付{amount}元")
class Order:
def __init__(self, strategy):
self.strategy = strategy
def checkout(self, amount):
self.strategy.pay(amount)策略模式让我明白了:面对变化,不要用if-else,要用多态。
2. 观察者模式(Observer)
观察者模式也是非常常用的模式。
观察者模式的核心是:定义对象间的一对多依赖,当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。
比如,用户注册成功后,要发欢迎邮件、发短信、送优惠券。如果把这些逻辑都写在注册方法里,注册方法会越来越臃肿。用观察者模式,注册成功后发一个事件,邮件、短信、优惠券的监听器各自处理。
现在的消息队列(Kafka、RabbitMQ)、事件总线,本质上都是观察者模式的延伸。
3. 装饰器模式(Decorator)
装饰器模式的核心是:动态地给一个对象添加额外的职责。
比如,一杯咖啡,可以加奶、加糖、加摩卡。如果用继承,会有无数个子类(咖啡+奶、咖啡+糖、咖啡+奶+糖……)。用装饰器模式,每个配料是一个装饰器,动态组合。
Python的装饰器语法,就是装饰器模式的语言级支持。Java的IO流(InputStream、BufferedInputStream、DataInputStream),也是装饰器模式的经典应用。
装饰器模式让我明白了:扩展功能,不一定要用继承,组合更灵活。
4. 单例模式(Singleton)
单例模式是最简单、也是最有争议的模式。
单例模式保证一个类只有一个实例,并提供全局访问点。
很多人滥用单例,到处都是单例,导致代码耦合严重、难以测试。但单例本身没有错,用在合适的场景(比如配置管理、连接池、日志)是很好的。
单例的实现方式也有很多:饿汉式、懒汉式、双重检查锁、静态内部类、枚举。每种方式都有优缺点。
单例模式让我明白了:没有不好的模式,只有用错地方的模式。
5. 外观模式(Facade)
外观模式的核心是:为子系统中的一组接口提供一个一致的界面。
比如,一个复杂的子系统,有很多个类和接口。客户端直接调用的话,需要了解很多细节。用外观模式,提供一个简单的接口,封装子系统的复杂性。
我们平时写的Service层、Controller层,某种程度上就是外观模式。外观模式让客户端和子系统解耦,也让子系统更容易使用。
四、对软件设计的思考
读《设计模式》,让我对软件设计有了更深的思考。
1. 设计是为了应对变化
软件设计的核心,是应对变化。
如果需求永远不变,那代码怎么写都行。但现实是,需求一直在变。好的设计,就是让代码更容易适应变化。
设计模式,就是教我们如何设计出容易变化的代码。通过抽象和封装,把变化的部分隔离出来,让变化不影响其他部分。
2. 好的设计是"简单"的
很多人以为,好的设计就是用了很多模式、很复杂的架构。但实际上,好的设计是简单的。
设计模式的目的,是简化代码,而不是复杂化。如果用了模式之后,代码更复杂了,那一定是用错了。
好的设计,是用最简单的方式,解决问题。模式只是工具,不是目的。
3. 设计是权衡的艺术
软件设计,没有绝对的对错,只有权衡。
- 用策略模式,增加了类的数量,但提高了可扩展性
- 用单例,方便了全局访问,但增加了耦合
- 用观察者模式,实现了解耦,但增加了调试的难度
每个模式都有优缺点,选择哪个模式,取决于具体的场景和需求。没有最好的设计,只有最合适的设计。
4. 过度设计是大忌
设计模式用多了,容易过度设计。
为了用模式而用模式,为了"可扩展"而加了很多抽象层,结果代码变得复杂难懂,而那些"可能的扩展"永远不会到来。
好的设计,是恰到好处。够用就好,不要为了不确定的未来,过度设计现在的代码。
5. 设计是持续演进的
设计不是一次性的,而是持续演进的。
最开始,代码很简单。随着需求变化,代码变得复杂。这时候,再重构,引入设计模式。然后,需求继续变化,代码继续演进。
不要一开始就追求完美的设计。先写简单的代码,等需求变化了,再重构。这就是"敏捷设计"的思想。
五、设计模式的局限
客观地说,设计模式也有一些局限。
1. 面向对象的局限
设计模式是面向对象时代的产物,主要解决面向对象编程中的问题。
现在,函数式编程越来越流行。在函数式编程中,很多设计模式不需要了,或者有了不同的实现方式。比如,策略模式在函数式编程中就是传一个函数;观察者模式可以用响应式流。
但设计模式的思想(解耦、封装、应对变化),在函数式编程中依然适用。
2. 语言特性的发展
很多编程语言,已经把设计模式做成了语言特性。
- Python的装饰器语法 = 装饰器模式
- C#的事件 = 观察者模式
- Java的枚举 = 单例模式
- Rust的trait = 策略模式
当模式变成语言特性,我们就不需要手动实现了。但理解模式的原理,能帮助我们更好地使用语言特性。
3. 有些模式已经不常用了
23种模式中,有些现在已经不常用了。
比如,解释器模式、中介者模式、访问者模式,在日常开发中用得比较少。有些模式,因为语言和框架的发展,已经被更好的方式替代了。
但了解这些模式,依然有价值。至少在遇到特定场景的时候,你知道有这样一种解决方案。
4. 容易被滥用
设计模式最大的问题,是容易被滥用。
很多人学了设计模式之后,到处用模式,不管适不适合。结果代码变得复杂难懂,维护成本很高。
记住:模式是工具,不是目的。用最简单的方式解决问题,才是最好的设计。
六、为什么今天依然值得读
虽然这本书出版快三十年了,但今天依然值得读。
1. 思想不过时
技术会变,语言会变,框架会变,但设计的思想不会变。
如何应对变化?如何解耦?如何封装?如何写出可维护的代码?这些问题,是永恒的。设计模式给出的答案,今天依然有效。
2. 是程序员的共同语言
设计模式的名字,已经成为程序员的共同语言。
当你说"这里用策略模式",大家都明白你的意思。当你说"这是一个单例",大家都知道怎么回事。
这种共同语言,提高了沟通效率。读这本书,能让你和其他程序员在同一个频道上交流。
3. 帮助理解框架源码
很多主流框架,都大量使用了设计模式。
- Spring:工厂模式、代理模式、观察者模式、模板方法模式
- MyBatis:装饰器模式、代理模式、组合模式
- JDK:迭代器模式、装饰器模式、观察者模式
读了设计模式,再看框架源码,就能理解作者为什么这么设计,而不是只看到一堆类和接口。
4. 提升设计能力
读设计模式,最终是为了提升自己的设计能力。
当你理解了23种模式,面对复杂的需求,你就有了更多的设计选择。你知道什么时候用什么模式,知道每种模式的优缺点,能做出更好的设计决策。
这种能力,是高级程序员和初级程序员的重要区别。
七、阅读建议
如果你打算读这本书,给你几个建议。
1. 不要死记硬背
不要试图记住23种模式的UML图和代码。记不住的,也没必要记。
重点是理解每个模式解决什么问题、核心思想是什么、什么时候用。理解了思想,代码可以自己写出来。
2. 结合实际项目
读的时候,结合自己的项目想想:这个模式在我的项目里能用吗?我以前写的代码,是不是可以用这个模式优化?
结合实际,理解会更深。
3. 先读常用的模式
23种模式,不需要一次读完。先读最常用的:
- 创建型:工厂方法、单例、建造者
- 结构型:适配器、装饰器、外观、代理
- 行为型:策略、观察者、模板方法、迭代器
这些模式掌握了,其他模式用到的时候再查。
4. 读代码,重构代码
读完之后,找一段自己的旧代码,看看能不能用设计模式重构。
实践是最好的学习。通过重构,你会真正理解模式的价值。
5. 多读几遍
这本书,读一遍是不够的。
第一次读,记住模式的名字。第二次读,理解模式的思想。第三次读,能在实际中灵活运用。
不同的阶段读,会有不同的体会。
八、写在最后
《设计模式》是一本经典的书,也是一本容易被误解的书。
很多人觉得它过时了,觉得它太理论了,觉得它是面向对象时代的遗物。但当你真正读懂了,你会发现,它讲的不是模式,而是设计的思想。这些思想,在任何时代、任何语言下,都适用。
重读这本书,我最大的收获是:不再为了用模式而用模式,而是理解了模式背后的原则和思想。面对复杂的需求,我能更从容地选择合适的设计,而不是生搬硬套。
2022年了,软件行业变化很快。新语言、新框架、新架构层出不穷。但有些东西是不变的:如何写出好代码,如何设计出易维护的系统,如何应对变化。这些,正是《设计模式》教给我们的。
最后,用一句话总结我的感受:"设计模式不是银弹,而是工具箱。理解每个工具的用途,在合适的时候用合适的工具,才是真正的设计能力。"
愿我们都能写出优雅、可维护、易扩展的代码。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录