常见设计模式
一、 创建型模式 (Creational Patterns) - 这些模式抽象了实例化过程,帮助系统独立于如何创建、组合和表示它的对象。
-
单例模式 (Singleton)
- 意图: 确保一个类只有一个实例,并提供一个全局访问点来获取这个唯一的实例。
- 解决的问题: 当系统中某个类只需要一个实例来协调全局行为时(例如配置管理器、日志记录器、线程池、数据库连接池),避免产生多个实例造成资源浪费或状态不一致。
- 核心结构:
- 私有化构造函数,防止外部直接
new
。 - 类内部维护一个静态的私有实例。
- 提供一个公有的静态方法(通常命名为
getInstance()
)来返回这个唯一的实例。需要考虑懒汉式(延迟加载,注意线程安全)和饿汉式(类加载时即创建)等不同实现。
- 私有化构造函数,防止外部直接
- 优点: 保证实例唯一性,节约资源,提供全局访问点。
- 缺点: 增加了全局状态,可能隐藏对象间的依赖关系,对测试不友好(特别是需要模拟或替换单例时),违反单一职责原则(既管理自身实例又执行业务逻辑)。
- 场景举例:
java.lang.Runtime
,java.awt.Desktop
, 日志框架中的 Logger 对象。
-
工厂方法模式 (Factory Method)
- 意图: 定义一个创建对象的接口,但让子类决定要实例化哪个类。工厂方法让类的实例化推迟到子类进行。
- 解决的问题: 当一个类不知道它所需要的对象的具体类型,或者想由其子类来指定创建的对象时。消除代码中与具体产品类的硬编码耦合。
- 核心结构:
Product
(产品接口): 定义了工厂方法所创建的对象的接口。ConcreteProduct
(具体产品): 实现Product
接口。Creator
(创建者/工厂接口或抽象类): 声明工厂方法factoryMethod()
,该方法返回一个Product
对象。可以包含依赖于Product
的其他业务逻辑。ConcreteCreator
(具体创建者/工厂): 重写factoryMethod()
以返回一个ConcreteProduct
实例。
- 优点: 客户端代码与具体产品解耦,易于扩展(增加新产品只需增加对应的具体产品类和具体工厂类),符合开闭原则。
- 缺点: 每增加一个产品,就需要增加一个对应的具体工厂类,导致类的个数成倍增加。
- 场景举例: Java
Collection
框架中的iterator()
方法;各种框架中用于创建不同类型组件的工厂。
-
抽象工厂模式 (Abstract Factory)
- 意图: 提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。
- 解决的问题: 当系统需要独立于其产品的创建、组合和表示时,或者需要处理多个“产品族”(例如,为不同操作系统提供一套风格一致的UI组件)。
- 核心结构:
AbstractFactory
(抽象工厂): 声明一组用于创建抽象产品的方法(每个方法对应一个产品类型)。ConcreteFactory
(具体工厂): 实现AbstractFactory
接口,负责创建具体的产品族。AbstractProduct
(抽象产品): 为一类产品对象声明一个接口。ConcreteProduct
(具体产品): 实现AbstractProduct
接口,由对应的具体工厂创建。Client
: 仅使用AbstractFactory
和AbstractProduct
接口。
- 优点: 隔离了具体类的生成,易于交换产品族(只需改变具体工厂),保证了同一工厂生产的产品是相互匹配兼容的。
- 缺点: 难以支持新种类的产品(如果要增加一个新的产品类型,需要修改所有工厂接口及其实现类)。
- 场景举例: 创建跨平台的UI库(
WindowsUIFactory
,MacUIFactory
分别创建WindowsButton
/MacButton
,WindowsTextBox
/MacTextBox
);数据库访问层,为不同数据库(如 MySQL, Oracle)提供Connection
,Statement
等对象的工厂。
-
建造者模式 (Builder)
- 意图: 将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
- 解决的问题: 当一个对象的创建涉及多个步骤或包含多个可选部分,且这些步骤或部分的组合方式多样时,避免出现“重叠构造器”(Telescoping Constructor)或构造函数参数列表过长的问题。
- 核心结构:
Builder
(建造者接口): 定义创建产品各个部分的接口(如buildPartA()
,buildPartB()
)以及一个返回最终产品的方法(如getResult()
)。ConcreteBuilder
(具体建造者): 实现Builder
接口,负责构建和装配产品的各个部件,并提供获取最终产品的方法。Product
(产品): 表示被构建的复杂对象。Director
(指挥者,可选): 负责按一定顺序调用Builder
接口来构建产品。客户端可以直接使用Builder
,也可以通过Director
。
- 优点: 使得构建代码与表示代码分离,可以更精细地控制构建过程,易于扩展(增加新的具体建造者),提高了代码可读性。
- 缺点: 增加了类的数量(需要创建 Builder 类),如果产品结构简单,则显得有点冗余。
- 场景举例:
java.lang.StringBuilder
的append()
方法链式调用;构建复杂的配置对象;生成不同格式的文档(如XML, JSON)。
-
原型模式 (Prototype)
- 意图: 使用原型实例指定要创建对象的类型,并通过复制这个原型来创建新对象。
- 解决的问题: 当创建对象的成本很高(如初始化耗时、涉及复杂的依赖或数据库操作),或者需要动态地根据运行时状态决定创建哪个类的实例时。
- 核心结构:
Prototype
(原型接口): 声明一个克隆自身的接口(通常是clone()
方法)。ConcretePrototype
(具体原型): 实现Prototype
接口的克隆方法。Client
: 创建一个新对象时,请求原型对象克隆自身。
- 优点: 隐藏了对象创建的复杂性,性能可能优于直接
new
(特别是对象初始化复杂时),可以动态添加或删除产品。 - 缺点: 每个需要克隆的类都必须实现克隆接口,克隆复杂对象(特别是包含循环引用或需要深拷贝时)可能比较困难和易错。
- 场景举例: Java 中的
Object.clone()
(需要实现Cloneable
接口);需要大量创建相似对象,如游戏中的敌人、粒子效果。
二、 结构型模式 (Structural Patterns) - 这些模式关注如何组合类和对象以形成更大的结构。
-
适配器模式 (Adapter)
- 意图: 将一个类的接口转换成客户端所期望的另一个接口。使得原本接口不兼容的类可以协同工作。
- 解决的问题: 当需要复用一个已有的类,但它的接口与系统其他部分期望的接口不一致时。
- 核心结构:
Target
(目标接口): 客户端期望使用的接口。Adaptee
(被适配者): 需要被适配的、具有不兼容接口的类。Adapter
(适配器): 实现Target
接口,内部包装(持有)一个Adaptee
对象的实例(对象适配器,常用)或继承Adaptee
类(类适配器,需要多重继承支持,Java 中可用接口实现)。Adapter
将Target
接口的调用转换为对Adaptee
接口的调用。
- 优点: 提高了类的复用性,增加了灵活性(可以适配不同的 Adaptee),符合开闭原则(不修改现有代码)。
- 缺点: 增加了系统的复杂性,引入了额外的类。
- 场景举例:
java.util.Arrays#asList()
;java.io
中的InputStreamReader(InputStream)
和OutputStreamWriter(OutputStream)
,将字节流适配成字符流。
-
装饰器模式 (Decorator)
- 意图: 动态地给一个对象添加一些额外的职责。就扩展功能而言,装饰器模式比生成子类更为灵活。
- 解决的问题: 当需要为一个对象添加功能,但又不想通过继承的方式(继承是静态的,可能导致子类爆炸)时。
- 核心结构:
Component
(组件接口): 定义了原始对象和装饰后对象的共同接口。ConcreteComponent
(具体组件): 实现Component
接口,是被装饰的原始对象。Decorator
(装饰器抽象类): 实现Component
接口,并持有一个Component
对象的引用(指向被装饰的对象)。ConcreteDecorator
(具体装饰器): 继承Decorator
,负责向Component
添加新的职责。它可以在调用被装饰对象的方法前后添加自己的行为。
- 优点: 比继承更灵活(运行时动态添加/删除职责),避免了类爆炸问题,可以将功能分解成多个可复用的小类。
- 缺点: 可能产生很多细粒度的小对象,增加了系统的复杂性,多层装饰可能导致调试困难。
- 场景举例:
java.io
包中的各种 FilterInputStream/FilterOutputStream 子类(如BufferedInputStream
,DataInputStream
);GUI 库中为组件添加边框、滚动条等。
-
代理模式 (Proxy)
- 意图: 为其他对象提供一种代理以控制对这个对象的访问。
- 解决的问题: 当直接访问某个对象不方便或不符合需求时,例如对象在远程服务器上、创建成本高需要延迟加载、需要控制访问权限等。
- 核心结构:
Subject
(主题接口): 定义了RealSubject
和Proxy
的共同接口,这样任何使用RealSubject
的地方都可以使用Proxy
。RealSubject
(真实主题): 定义了Proxy
所代表的真实对象。Proxy
(代理): 保存一个引用使得代理可以访问实体,并提供一个与Subject
接口相同的接口。代理可以控制对RealSubject
的访问,并可能负责创建和删除它。常见的有:远程代理、虚拟代理、保护代理、智能引用等。
- 优点: 代理对象可以起到中介和保护作用,可以增强目标对象的功能(如懒加载、权限控制、日志记录)。
- 缺点: 增加了系统的复杂性,可能引入额外的间接层导致请求处理变慢。
- 场景举例: Java RMI (远程代理);Hibernate 延迟加载 (虚拟代理);Spring AOP (通过动态代理实现)。
-
外观模式 (Facade)
- 意图: 为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。
- 解决的问题: 当一个系统包含许多复杂的类和交互时,客户端直接与这些类交互会非常困难和混乱。需要提供一个简化的入口点。
- 核心结构:
Facade
(外观): 知道哪些子系统类负责处理请求,将客户端的请求委派给适当的子系统对象。它对客户端隐藏了子系统的复杂性。Subsystem classes
(子系统类): 实现子系统的功能,处理由Facade
对象指派的任务。它们不了解外观的存在。
- 优点: 简化了客户端与复杂子系统之间的交互,降低了耦合度,使得子系统内部的修改对客户端影响变小。
- 缺点: 可能不符合开闭原则(修改子系统功能可能需要修改 Facade),Facade 类可能变成一个“上帝类”。
- 场景举例: 设计一个启动家庭影院的按钮,它内部协调了投影仪、DVD 播放器、音响、灯光等多个设备;JDBC 封装了与具体数据库驱动的复杂交互。
-
桥接模式 (Bridge)
- 意图: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
- 解决的问题: 当一个类存在两个或多个独立变化的维度(例如,形状和颜色),如果使用继承会导致类的数量呈指数级增长。
- 核心结构:
Abstraction
(抽象类): 定义了抽象类的接口,并维护一个指向Implementor
类型对象的引用。RefinedAbstraction
(扩充抽象类): 扩展Abstraction
,实现或重写父类的方法。Implementor
(实现类接口): 定义实现类的接口,该接口不一定要与Abstraction
的接口完全一致。ConcreteImplementor
(具体实现类): 实现Implementor
接口。
- 优点: 分离了抽象和实现,使得它们可以独立扩展,提高了系统的灵活性和可扩展性。
- 缺点: 增加了系统的理解和设计难度。
- 场景举例: JDBC 驱动程序(
DriverManager
是抽象部分,各种数据库驱动是实现部分);不同类型的图形(Shape
)可以应用不同的绘图 API(DrawingAPI
)。
-
组合模式 (Composite)
- 意图: 将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
- 解决的问题: 处理具有层级结构的数据(如文件系统、组织架构、GUI 容器)。希望客户端代码能够统一处理简单元素(叶子)和复杂元素(容器)。
- 核心结构:
Component
(组件接口): 声明了组合中对象(叶子和容器)的通用接口,包括管理子组件的方法(如add
,remove
,getChild
)和业务操作方法。Leaf
(叶子): 表示组合中的基本对象,没有子节点。实现了Component
接口的业务方法,管理子组件的方法通常为空实现或抛出异常。Composite
(容器): 表示组合中的容器对象,可以包含子节点(Leaf
或其他Composite
)。实现了Component
接口的所有方法,其业务方法通常会委托给子组件执行。
- 优点: 简化了客户端代码(可以一致地处理叶子和容器),易于增加新的
Component
类型。 - 缺点: 使得设计过于一般化(难以限制容器中能添加的组件类型),可能需要在运行时检查类型。
- 场景举例: 文件系统(文件是 Leaf,目录是 Composite);GUI 中的控件布局(窗口/面板是 Composite,按钮/文本框是 Leaf);组织架构图。
-
享元模式 (Flyweight)
- 意图: 运用共享技术有效地支持大量细粒度的对象。通过共享不变的部分(内部状态),只维护可变的部分(外部状态)。
- 解决的问题: 当系统中存在大量相似的对象,导致内存消耗过大时。
- 核心结构:
Flyweight
(享元接口): 声明了享元对象接受外部状态并执行操作的方法。ConcreteFlyweight
(具体享元): 实现了Flyweight
接口,包含了可共享的内部状态。它必须是不可变的或状态不随上下文改变。UnsharedConcreteFlyweight
(非共享具体享元,可选): 并非所有Flyweight
子类都需要共享。FlyweightFactory
(享元工厂): 创建并管理Flyweight
对象。它维护一个池,当客户端请求一个Flyweight
时,工厂检查池中是否存在,存在则返回,不存在则创建新的并加入池中。Client
: 持有或计算Flyweight
对象的外部状态,并在需要时将外部状态传递给Flyweight
对象。
- 优点: 极大减少了内存中对象的数量,提高了性能。
- 缺点: 需要区分内部状态和外部状态,使得系统逻辑更复杂,外部状态的管理可能需要额外开销。
- 场景举例:
java.lang.String
的字符串常量池;java.lang.Integer#valueOf(int)
缓存了 -128 到 127 的整数对象;文本编辑器中的字符对象(字形是内部状态,位置、颜色是外部状态)。
三、 行为型模式 (Behavioral Patterns) - 这些模式关注对象之间的职责分配和算法封装。
-
策略模式 (Strategy)
- 意图: 定义一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
- 解决的问题: 当一个系统需要动态地在几种算法中选择一种时,或者需要将算法的实现细节与使用算法的类隔离开。避免使用大量的
if-else
或switch
语句。 - 核心结构:
Strategy
(策略接口): 定义所有支持的算法的公共接口。ConcreteStrategy
(具体策略): 实现Strategy
接口,封装了具体的算法或行为。Context
(上下文): 持有一个Strategy
对象的引用,并将请求委托给它。Context
不直接执行任务,而是调用策略对象的方法。可以提供方法来更换当前的策略对象。
- 优点: 易于扩展(增加新策略只需添加具体策略类),避免了多重条件判断,提高了代码的灵活性和可维护性。
- 缺点: 客户端必须知道所有的策略类并自行决定使用哪一个(虽然可以通过工厂等模式简化),增加了对象的数量。
- 场景举例:
java.util.Comparator
接口用于Collections.sort()
;电商平台的多种支付方式(支付宝、微信、银行卡);不同的数据压缩算法。
-
模板方法模式 (Template Method)
- 意图: 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 解决的问题: 当多个类有相似的算法结构,但某些步骤的具体实现不同时。固定算法流程,提取公共代码。
- 核心结构:
AbstractClass
(抽象类): 定义了一个templateMethod()
,它实现了算法的骨架,调用一个或多个抽象的primitiveOperation()
(必须由子类实现)和可选的hookOperation()
(子类可选择性覆盖的钩子方法,提供默认实现)。ConcreteClass
(具体类): 继承AbstractClass
,实现父类中定义的抽象primitiveOperation()
,以完成算法中特定于子类的步骤。可以覆盖hookOperation()
。
- 优点: 代码复用(公共部分在父类实现),控制了子类的扩展点(固定了算法结构),符合开闭原则(对扩展开放,对修改封闭)。
- 缺点: 子类必须严格按照父类定义的流程来实现。
- 场景举例: Java Servlet 框架中的
doGet()
,doPost()
方法;java.io.InputStream
中的read()
方法模板;JUnit
测试框架的setUp()
,tearDown()
。
-
观察者模式 (Observer) (也叫发布-订阅模式, Publish-Subscribe)
- 意图: 定义对象之间的一种一对多的依赖关系,当一个对象(主题/被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都得到通知并自动更新。
- 解决的问题: 当一个对象状态的改变需要通知其他不确定数量的对象,且不想让这些对象与被通知的对象紧密耦合时。
- 核心结构:
Subject
(主题/被观察者接口): 提供用于注册(attach
)、注销(detach
)和通知(notify
)观察者对象的接口。ConcreteSubject
(具体主题): 实现了Subject
接口,维护自身的状态,并在状态改变时向已注册的观察者发出通知。Observer
(观察者接口): 定义了一个更新接口(通常是update()
方法),用于在接收到主题通知时更新自身。ConcreteObserver
(具体观察者): 实现了Observer
接口,维护一个指向ConcreteSubject
对象的引用(可选,用于获取状态),并在update()
方法中实现具体的更新逻辑。
- 优点: 实现了主题和观察者之间的松耦合,支持广播通信,易于增加新的观察者。
- 缺点: 如果观察者数量过多或更新逻辑复杂,可能影响性能;可能导致复杂的级联更新;观察者不知道其他观察者的存在。
- 场景举例: Java中的
java.util.Observable
和java.util.Observer
(虽然现在推荐使用更现代的事件机制);GUI 事件监听(如按钮点击事件);消息队列;MVC 模式中的 Model 和 View 之间的交互。
-
迭代器模式 (Iterator)
- 意图: 提供一种方法来顺序访问一个聚合对象(集合)中的各个元素,而不需要暴露该对象的内部表示。
- 解决的问题: 需要遍历一个集合对象,但又不想暴露其内部数据结构(如数组、链表、树等)。希望为不同的集合提供统一的遍历接口。
- 核心结构:
Iterator
(迭代器接口): 定义了访问和遍历元素所需的操作,如hasNext()
,next()
,remove()
(可选)。ConcreteIterator
(具体迭代器): 实现了Iterator
接口,负责跟踪遍历过程中的当前位置。Aggregate
(聚合接口): 定义了创建迭代器对象的接口(通常是createIterator()
方法)。ConcreteAggregate
(具体聚合): 实现了Aggregate
接口,返回一个ConcreteIterator
实例。它持有实际的集合对象。
- 优点: 分离了集合对象的遍历行为,简化了聚合类的接口,支持多种遍历方式。
- 缺点: 对于简单的集合,引入迭代器可能显得过于复杂。
- 场景举例: Java
Collection
框架的所有集合类都实现了Iterable
接口(对应Aggregate
),并提供iterator()
方法(对应createIterator()
)返回Iterator
对象。
-
责任链模式 (Chain of Responsibility)
- 意图: 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
- 解决的问题: 当一个请求需要由多个对象中的一个来处理,但不确定是哪个对象,或者希望动态指定处理者序列时。
- 核心结构:
Handler
(处理者接口或抽象类): 定义了处理请求的接口(如handleRequest()
),并通常包含一个指向链中下一个处理者(successor
)的引用以及设置下一个处理者的方法。ConcreteHandler
(具体处理者): 实现了Handler
接口。它判断自己是否能处理该请求,如果能则处理,否则将请求转发给它的后继者。
- 优点: 降低了发送者和接收者之间的耦合度,增强了系统的灵活性(可以动态地组合链条),符合开闭原则。
- 缺点: 请求不保证一定会被处理;链条过长可能影响性能;调试时追踪请求的处理过程可能比较困难。
- 场景举例: Java Servlet 框架中的
Filter
链;软件中的异常处理机制;工作流/审批流程。
-
命令模式 (Command)
- 意图: 将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
- 解决的问题: 需要将请求的发送者与接收者解耦,或者需要将操作本身作为对象来处理(例如,支持撤销/重做、排队、宏命令)。
- 核心结构:
Command
(命令接口): 声明一个执行操作的接口(通常是execute()
方法),可能还有undo()
方法。ConcreteCommand
(具体命令): 实现了Command
接口,它持有一个Receiver
对象的引用,并在execute()
方法中调用Receiver
的相应操作。Invoker
(调用者): 持有Command
对象,并在需要时调用其execute()
方法来发起请求。不关心谁是接收者。Receiver
(接收者): 知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。Client
: 创建ConcreteCommand
对象并设置其Receiver
。
- 优点: 降低了耦合度,易于增加新的命令,易于实现撤销/重做、事务、队列等功能。
- 缺点: 可能导致系统中产生大量具体的命令类。
- 场景举例: GUI 按钮点击事件的处理;线程池中的任务 (
Runnable
可以看作一种简单的命令);编辑器的撤销/重做功能;模拟事务操作。
-
备忘录模式 (Memento)
- 意图: 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
- 解决的问题: 需要保存和恢复对象的状态,但又不希望暴露对象的内部实现细节。
- 核心结构:
Originator
(发起人): 需要被保存状态的对象。它创建一个包含其当前内部状态快照的Memento
对象,并可以使用Memento
对象来恢复其内部状态。Memento
(备忘录): 存储Originator
对象的内部状态。Memento
应保护其内容不被Originator
以外的对象访问(通常通过将状态设为包私有或使用内部类实现)。它通常只有获取状态的方法,没有设置状态的方法(或者设置方法只对 Originator 可见)。Caretaker
(负责人): 负责保存好Memento
对象,但不能操作或检查Memento
的内容。它只知道何时保存和何时恢复Originator
的状态。
- 优点: 保持了
Originator
的封装性,简化了Originator
(无需管理历史状态),将状态存储逻辑移到Caretaker
。 - 缺点: 如果
Originator
状态很大,Memento
对象可能会消耗大量内存;Caretaker
需要管理Memento
的生命周期。 - 场景举例: 文本编辑器的撤销/重做功能;数据库事务的回滚;游戏存档/读档。
-
状态模式 (State)
- 意图: 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
- 解决的问题: 当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时。避免使用庞大、臃肿的
if-else
或switch
语句来处理基于状态的行为。 - 核心结构:
Context
(上下文): 定义了客户端感兴趣的接口,并维护一个State
对象的实例,这个实例定义了对象的当前状态。将所有与状态相关的请求委托给当前状态对象处理。可以提供方法来改变当前状态。State
(状态接口或抽象类): 定义了一个接口以封装与Context
的一个特定状态相关的行为。ConcreteState
(具体状态): 实现了State
接口,每一个子类实现一个与Context
的一种状态相关的行为。它们通常负责在适当的时候改变Context
的当前状态。
- 优点: 将与特定状态相关的行为局部化到单独的类中,使得状态转换更加明确,消除了庞大的条件分支语句,符合开闭原则。
- 缺点: 增加了类的数量,如果状态很多,会导致类爆炸。
- 场景举例: TCP 连接状态(Listen, Synced, Established, Closed);订单处理流程(待支付、已支付、已发货、已完成、已取消);自动售货机的状态。
-
访问者模式 (Visitor)
- 意图: 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
- 解决的问题: 当你需要对一个对象结构(通常是组合模式形成的结构)中的多种不同类型的元素执行多种不同的、不相关的操作,但又不希望在每个元素类中添加这些操作(这会污染元素类,且难以增加新操作)。
- 核心结构:
Visitor
(访问者接口): 为对象结构中每一种ConcreteElement
声明一个visit()
操作。ConcreteVisitor
(具体访问者): 实现Visitor
接口中声明的每个操作,封装了一种对元素的操作算法。Element
(元素接口): 定义一个accept(Visitor)
方法,它以一个访问者作为参数。ConcreteElement
(具体元素): 实现Element
接口的accept()
方法,通常是调用visitor.visit(this)
,将自身传递给访问者。ObjectStructure
(对象结构): 通常是一个元素的集合(如列表、树),可以枚举它的元素,并提供一个高层接口允许访问者访问它的元素。
- 优点: 易于增加新的操作(只需增加新的
ConcreteVisitor
),将相关的操作集中到一个访问者类中,符合开闭原则(对增加操作开放)。 - 缺点: 难以增加新的
ConcreteElement
类型(需要修改所有Visitor
接口及其实现),破坏了元素的封装性(Visitor
通常需要访问元素的内部状态)。 - 场景举例: 编译器中对语法树的不同处理(类型检查、代码生成);对复杂文档结构执行不同操作(如计算字数、生成目录、导出不同格式)。
-
中介者模式 (Mediator)
- 意图: 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 解决的问题: 当对象之间存在复杂的网状通信结构(多对多关系),导致对象之间高度耦合,难以理解和维护时。
- 核心结构:
Mediator
(中介者接口): 定义了Colleague
对象之间进行通信的接口。ConcreteMediator
(具体中介者): 实现了Mediator
接口,协调各个Colleague
对象的交互。它了解并维护所有的Colleague
对象。Colleague
(同事类接口或抽象类): 定义了同事对象的接口,每个同事类都知道它的Mediator
对象。ConcreteColleague
(具体同事类): 实现Colleague
接口。每个同事对象在需要与其他同事通信时,都与它的中介者通信,而不是直接与其他同事交互。
- 优点: 降低了类间的耦合(从网状变为星形),将交互的复杂性集中在中介者,提高了对象的可复用性。
- 缺点: 中介者可能变得过于复杂,成为一个“上帝类”,难以维护。
- 场景举例: GUI 应用程序中对话框里的控件交互(如一个按钮的启用/禁用状态取决于其他复选框或文本框的内容);聊天室系统中服务器作为中介者转发消息。
-
解释器模式 (Interpreter)
- 意图: 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
- 解决的问题: 当有一个简单的语言需要被解释执行,并且可以将该语言中的句子表示为一个抽象语法树时。
- 核心结构:
AbstractExpression
(抽象表达式): 声明一个interpret()
操作,所有具体表达式(终结符和非终结符)都实现这个接口。TerminalExpression
(终结符表达式): 实现了AbstractExpression
接口,代表文法中的终结符,解释时通常直接返回结果。NonterminalExpression
(非终结符表达式): 实现了AbstractExpression
接口,代表文法中的非终结符。它通常包含对其他AbstractExpression
的引用,其interpret()
方法会递归调用子表达式的interpret()
方法。Context
(上下文): 包含解释器之外的一些全局信息。Client
: 构建(或被给定)一个代表特定句子的抽象语法树,并调用interpret()
操作。
- 优点: 易于改变和扩展文法(通过增加新的表达式类),易于实现语言。
- 缺点: 对于复杂的文法,类层次结构可能会变得庞大且难以维护,性能可能不是最优。
- 场景举例: 正则表达式引擎;SQL 解析器;用于特定领域语言 (DSL) 的解释。这个模式在通用开发中相对用得较少。
希望这些更详细的解释能帮助你更好地理解这些常见的设计模式!记住,理解模式的意图和它所解决的问题是关键,而不是死记硬背结构。在实践中灵活运用,并权衡其带来的好处与引入的复杂性。
Last updated on