微服务架构落地后,小明发现团队写的代码五花八门——同样是创建对象,有人new、有人反射、有人工厂;同样是扩展功能,有人继承、有人包装、有人硬塞。代码越写越乱,改一个功能牵连一堆类。
大牛哥递给他一本《设计模式:可复用面向对象软件的基础》:"这是四人组(Gang of Four, GoF)踩了无数坑总结的武功秘籍,23招式,专治各种代码乱象。不过——学的是思想,不是死记硬套。"
📖 10.1 设计模式概述
设计模式概念源自建筑师Christopher Alexander——"模式=描述一个不断发生的问题和该问题的解决方案"。GoF四人组将其引入软件领域,定义:对在特定场景下解决一般设计问题的类和互相通信的对象的描述。通俗说=某一类问题的通用解决方案。
设计模式四要素:
- 📛 模式名称:名不正则言不顺
- ❓ 问题:该模式意图解决的问题,超出就不该用
- 💡 解决方案:解决问题的方法描述
- 📊 效果:更具体的效果描述
学习设计模式两个注意:
- 学思想而非死记——设计模式蕴含软件设计思想,和架构风格一致(MVC既是设计模式也是架构风格)
- 不能滥用——不相宜场合乱用有害无益,过度使用让架构混乱难维护。设计模式主要解决对象间通信和依赖的结构关系
23个GoF模式速览(按三大分类):
创建型(解决对象创建问题):
- 🏭 Factory Method——延迟创建,子类决定创建哪个类
- 🏗️ Abstract Factory——一致接口创建一系列对象
- 🧱 Builder——逐步构造复杂对象,创建与表示分离
- 🖨️ Prototype——深复制原型创建新对象
- 💎 Singleton——保证一个类仅有一个实例
结构型(解决类与对象的组织结构):
- 🔌 Adapter——接口转换,解决不相容
- 🌉 Bridge——抽象与实现分离,独立变化
- 🌳 Composite——树形组合,单个与组合一致
- 🎨 Decorator——动态为方法增加功能
- 🚪 Facade——一组类的一致访问接口
- 🪶 Flyweight——共享大量细粒度对象
- 🕶️ Proxy——访问代理,控制访问
行为型(解决类与对象如何通信):
- 📜 Interpreter——解释器解释句子
- 📋 Template Method——操作模板,步骤在子类实现
- ⛓️ Chain of Responsibility——对象链传递请求
- 📨 Command——请求封装为对象
- 🔢 Iterator——顺序访问集合元素
- 🤝 Mediator——中介对象封装通信,降低耦合
- 📸 Memento——捕获对象状态,不破坏封装
- 👁️ Observer——状态变化广播给观察者
- 🔄 State——状态改变时改变行为
- 🎯 Strategy——算法变化独立于客户
- 🚶 Visitor——不改变元素类定义新操作
J2EE设计模式(GoF之后):Intercepting Filter(截取请求预处理)、Session Facade(Session Bean封装业务逻辑接口)等。
设计模式与软件架构的关系:架构更倾向整体全局描述软件组成(4+1视图等),设计模式更侧重类与类、对象与对象的关系。两者蕴含相同思想——消息总线架构风格与Observer模式"神似"。
🏗️ 10.2.1 Abstract Factory:抽象工厂
意图解决的问题:new ClassName()造成类名硬编码,无法根据运行环境动态加载相同接口但实现不同的类实例。为不同运行环境实现抽象类子类后,普通创建方式=代码与运行环境强绑定。
模式描述:AbstractFactory接受Client"订单"→用不同"车间"(ConcreteFactory)→根据"产品模型"(AbstractProduct)→生产特定"产品"(Product)。车间与产品一一对应。CreateProduct()方法与AbstractProduct一一对应。
效果:可配置的、动态的对象创建。提高移植性,尤其是多平台/多功能配置版本时。
相关讨论:核心思想=① 可配置的对象创建方法提高移植性和复用性 ② 充分利用多态特性避免硬编码。Java中可用面向接口方式简化——ProductFactory提供getProduct(productName),所有产品实现Product接口。增加新实现Product接口的产品不需要修改任何代码。
💎 10.2.2 Singleton:单件模式
意图解决的问题:一些服务类有且仅有一个实例——短消息服务、打印机服务、系统配置环境控制。全局变量只能保证编码时用了唯一实例,系统扩张后无法保证。
模式描述:构造函数设为protected/private防止外部直接初始化→通过getInstance()获得唯一实例→只创建一次uniqueInstance。两种初始化策略:Lazy Initialization(延迟初始化,第一次调用时创建)和Early Initialization(提前初始化,类加载时创建)。
注意三点:
- 仅保证单一系统内单一实例。分布式构件(多JVM下的EJB)上面的方法不能保证全系统唯一
- 避免滥用——很多过度设计的Singleton同static工具类一样没意义,反而降低效率
- 不支持继承——C++可用Template定义Singleton模板,Java/C#需逐一实现
🎨 10.2.3 Decorator:装饰模式(油漆工)
意图解决的问题:类功能不够强需要扩展?最简单=继承出新类。但会产生大量子类,类层次结构复杂混乱。
模式描述:DecoratorComponent和ConcreteComponent继承同一个抽象类Component,拥有相同接口。ConcreteDecorator1和ConcreteDecorator2是具体装饰类。运行期根据客户选择具有不同行为特征——动态扩展。
效果:动态扩展功能,避免大量子类,比继承更灵活。随时根据需求产生新装饰类,避免高层类复杂性。但大量使用会造成很多接口类似的小装饰对象,可维护性下降。
相关讨论:JDK的I/O API大量使用Decorator——new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")))就是层层装饰。开源项目displaytag也以Decorator方式扩展。
🚪 10.2.4 Facade/Session Facade:门面模式
意图解决的问题:客户程序用到多个子系统,需要了解每个子系统接口,结构混乱,耦合度大,扩展维护困难。
模式描述:在原有系统前增加一层,封装子系统接口,对外提供一致访问接口。封装的是子系统的内部结构。
Session Facade(J2EE扩展):EJB是分布式构件,访问=远程调用。即使Servlet和EJB在同一台机器,也要通过127.0.0.1访问,效率低。用Session Bean封装业务逻辑→减少远程调用次数→降低网络负载→提高性能。每次访问Session Bean=完成一次业务操作。
🤝 10.2.5 Mediator:中介者模式
意图解决的问题:复杂系统中对象相互通信,相互依赖。改一个影响多个,耦合关系造成可维护性降低。
模式描述:一组对象(Colleague子类)通过中介类(ConcreteMediator)通信。ConcreteMediator继承抽象Mediator,实现通信接口,封装Colleague之间的通信。
类比:Mediator像人的大脑——眼睛发送"前面有坑"消息→大脑接收→向脚发送"停止前进"消息。走路时和开车时,同样"前面有状况"的消息,大脑的响应不同(脚停/踩刹车)。
效果:降低Colleague间耦合,单个Colleague变化不影响其他对象。但Colleague多且关系复杂时,ConcreteMediator会变得非常复杂。仅在较小范围内使用,否则中介类难维护会抵消优点。
典型应用:GUI——窗体中多个需要通信的对象(部门下拉列表变化→人员下拉列表跟着变)。用Mediator封装通信。
👁️ 10.2.6 Observer:观察者模式
意图解决的问题:对象状态变化是独立的(A变化不会自动反映到B),但很多对象相互关联——如股票变化需同时反映到实时行情、技术分析图表。硬编码关联=紧密耦合。
模式描述:Subject(被观察者)保存观察者列表observers,通过attach/detach动态添加/移除观察者。状态变化时notify()通知所有观察者。ConcreteObserver继承自Observer抽象类。Subject了解有谁在观察但不了解观察者要做什么——抽象耦合。
效果:动态增减观察者简单。可实现消息广播。但广播副作用=增加系统负载,任何消息都被自动发送到每个观察者,未必都必要。
使用注意:
- setState()后自动notify()会导致连续多次状态变更触发多次通知→可能造成并发问题。可改为客户程序执行一系列变更后手动调用notify(),但不能保证一定被通知
- 避免广播风暴——update()中绝不能更新自己观察的Subject的状态,否则消息无穷循环
Smalltalk的MVC架构就用了Observer——Model变化传递到View。Observer模式实现一对多依赖的抽象耦合,广泛应用。
🔍 10.2.7 Intercepting Filter:筛选器模式
意图解决的问题:MVC Web应用中,客户请求到达控制器前需预处理(身份验证、请求来源验证、解码等)。放控制器里=控制器复杂且难维护。
模式描述:FilterManager调度FilterChain→拦截请求→FilterChain中过滤器依次预处理→最后一个过滤器完成后→FilterManager转发给实际目标(Target)。
效果:预处理逻辑和真正处理逻辑分离。Target只关心具体逻辑,预处理放FilterManager。解除两类处理耦合,扩展修改预处理容易。
相关讨论:与Decorator类似——都在处理对象前增加一层扩展操作,但实现差别大。可让Filter更通用可配置——不同请求用不同FilterChain,可用工厂模式动态创建Filter对象。这就是模式组合使用的例子。
🎯 10.3 设计模式总结
本章详细讨论了7种模式:Abstract Factory、Singleton、Decorator、Facade/Session Facade、Mediator、Observer、Intercepting Filter。但设计模式远不止这些——23种GoF模式外,很多学者还在不断总结。
学习设计模式最重要的是理解,而非生搬硬套。每种模式都包含良好的设计思想——隐藏内部细节、降低耦合度等。观察人类社会也能发现类似模式:人是封装良好的对象(感官+行动=接口);望远镜/电话扩展能力(Decorator);中介所提供交流平台(Mediator);新闻机构让你了解最新状态(Observer);经纪人是Proxy;组织结构是Composite……
使用中经常把几种模式综合——如Intercepting Filter+工厂模式。架构设计师要综合利用设计模式消除系统混乱与耦合。
老王最后问:"架构设计师最重要的能力是什么?"
小明笑了笑:"不是知道所有的模式和方法,而是知道在什么场景下选择什么、放弃什么。从软件生命周期到开发模型,从需求分析到架构设计,从质量属性到设计模式——架构师的工作始终围绕三个字:选、妥协、平衡。"
十章学完,从"手工作坊"到"现代工厂",从瀑布到敏捷,从模块设计到架构评估,从质量属性到设计模式。饿了么么从3人小作坊成长为微服务架构的现代系统。架构师之路永无止境——但每一步都算数。📚