# 设计模式
设计模式由GoF
在《设计模式:可复用面向对象软件的基础》一书在中提出。是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述。
在《设计模式:可复用面向对象软件的基础》一书中提出了23中设计模式,按照模式的目的可以分为以下三类:
- 创建型,用于描述怎样创建对象,主要特点是将对象的创建与使用分离,包括工厂模式、抽象工厂模式、建造者模式、单例模式和原型模式5种
- 结构型,用于描述如何将类或对象按某种布局组成更大的结构,包括适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式和享元模式7种
- 行为型,用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。包括策略模式,模板方法模式,发布订阅模式,迭代器模式,职责链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式
# 创建型
# 单例模式Singleton
单例模式,保证一个类仅有一个实例,并提供一个访问他的全局访问点。
# 工厂模式FactoryMethod
定义一个用于创建对象的接口,让子类决定实例化哪个类,将一个类的实例化延迟到其子类。
# 抽象工厂模式AbstractFactory
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们的具体类。
由一个抽象工厂和创建具体类的ConcreteFactory组成
用户仅与抽象类定义的接口交互,而不使用特定的具体类的接口。
抽象工厂AbstractFactory一般由工厂方法FactoryMethod实现。
# 建造者模式Builder
将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
与抽象工厂模式相比,建造者模式着重于一步步构造一个复杂对象,而抽象工厂模式着重于多个系列的产品对象。建造者模式在最后返回产品,而抽象工厂模式是立即返回的。组合模式通常是用Builder
生成的。
# 原型模式Prototype
将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
# 结构型
# 适配器模式Adapter
适配器模式的目的是为了解决对象之间的接口不兼容的问题,通过适配器模式可以不更改源代码的情况下,让两个原本不兼容的对象在调用时正常工作。
# 代理模式Proxy
代理模式是为一个对象提供一个代用品或者占位符,以便控制对它的访问。
代理模式的目的是为了控制对实体对象的访问。具体可分为:
- 远程代理
Remote Proxy
为一个对象在不同的地址空间提供局部代表 - 虚代理
Virtual Proxy
根据需要创建开销很大的对象 - 保护代理
Protection Proxy
控制对原始对象的访问,例如可以用于对象应该有不同的访问权限的情况下 - 智能指引
Smart Reference
在访问对象时执行一些附加的操作
# 装饰器模式Decorator
装饰器模式是一种动态的给对象增加职责的方式,能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
与继承相比,装饰器模式更加轻便灵活,是一种“即用即付”的方式。
# 享元模式Flyweight
享元模式是一种用于性能优化的模式,其核心是运用共享技术来有效支持大量细粒度的对象。
在面向对象语言中,状态模式和策略模式最好用享元模式来实现,以节省内存消耗。
# 组合模式Composite
组合模式就是由一些小的子对象构建出的更大的对象,而这些小的子对象本身可能也是由多个孙对象组合而成的。
组合模式将对象组合成树状结构,以表示“部分-整体”的层次结构。除了用来表示树状结构之外,组合模式的另一个好处就是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
# 桥接模式Bridge
将抽象与实现分离,使它们可以独立变化。用组合关系代替继承关系来实现,以降低抽象和实现的耦合度。
抽象工厂模式可以用来创建和配置一个特定的桥接模式。
适配器模式用来帮助无关的类协同工作,通常在系统设计完成之后才会被使用。而桥接模式则是在系统开始时就被使用,是为了抽象接口和实现部分可以独立进行改变。
# 外观模式Facade
为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
外观模式适用于以下情况:
- 需要为一个复杂的子系统提供一个简单接口时。对于复杂的子系统,为了使其更具可重用性和可定制性,运用的大部分设计模式都会产生更多更小的类,但也会系统使用的复杂度。外观模式可以降低只需要基础功能的用户的使用难度。
- 客户程序与抽象类的实现部分存在的很大的依赖性,外观模式可以将之与其他子系统和客户程序分离,提高子系统的独立性和可移植性
- 构建一个层次结构的子系统时,外观模式可以定义子系统每层入口点,并且使子系统之间进通过Facade进行通信,简化依赖关系
抽象工厂模式通常可以和外观模式一起使用,以提供一个接口,该接口以一种子系统独立的方式创建子系统对象。抽象工厂模式也可以代替外观模式隐藏与平台相关的类。
中介者模式与外观模式的相似之处是都抽象了一些已有的类的功能。但是中介者模式的目的是
外观模式只需要一个Facade
对象,通常是一个单例模式。
# 行为型
# 迭代器模式Iterator
提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
# 观察者模式Observer
用于定义对象间一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
# 命令模式Command
命令模式是指将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,并且可以对请求排队或记录日志,并支持可撤销的操作。
其中的命令command
指的是一个执行某些特定事情的指令。
命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
相对于过程化的请求调用,command
对象拥有更长的生命周期。对象的生命周期是跟初始请求无关的,因为这个请求已经被封装在了command
对象的方法中,成为了这个对象的行为,我们可以在程序运行的任意时刻去调用这个方法。此外,命令模式还支持撤销、排队等操作。
命令模式的由来,其实是回调callback
函数的一个面向对象的替代品。
const setCommand = (button, command) => {
button.onClick = () => command.execute()
}
const MenuBar = {
refresh(){
console.log('刷新菜单')
},
}
const RefreshMenuCommand = (receiver) => () => ({
execute(){
receiver.refresh()
},
// 撤销命令
undo(){
}
})
const refreshMenuCommand = RefreshMenuCommand(MenuBar)
// 假设html中已经存在id为button1的button
const button1 = document.getElementById('button1')
setCommand(button1, refreshMenuCommand)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
命令的撤消一般是给命令对象增加一个名为unexecude
或者undo
的方法,执行execute
的反向操作。
需要撤销一系列的命令时,可以把所有执行过的命令都储存在一个历史列表中,然后倒序循环来依次执行需要撤销的命令的undo
操作。
对于某些无法顺利地利用undo
操作让对象回到execute
之前状态的情况,可以通过先回退初始状态,再把执行过的命令全部重新执行一遍来实现。
# 策略模式Strategy
定义一系列的算法,把它们一个个的封装起来,并且使他们可以相互替换。
# 状态模式State
GoF中的定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
将状态封装成独立的类,并将请求委托给当前的状态对象,当对象的内部状态改变时,会带来不同的行为变化。
所使用的对象,在不同的状态下具有截然不同的行为,这个对象看起来是从不同的类中实例化而来的,实际上这是使用了委托的效果。
状态模式的优点:
- 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。
- 避免
Context
无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了Context
中原本过多的条件分支。 - 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然。
Context
中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响。
状态模式的缺点:
会在系统中定义许多状态类,而且系统中会因此而增加不少对象。另外,由于逻辑分散在状态类中,虽然避开了不受欢迎的条件分支语句,但也造成了逻辑分散的问题,我们无法在一个地方就看出整个状态机的逻辑。
状态模式和策略模式的异同
相同点是都有一个上下文、一些策略或者状态类,上下文把请求委托给这些类来执行。
区别是策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,所以客户必须熟知这些策略类的作用,以便客户可以随时主动切换算法;而在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情发生在状态模式内部。对客户来说,并不需要了解这些细节。
# 中介者模式Mediator
中介者模式的主要作用是解除对象之间的强耦合关系,通过增加一个中介者,让所有的对象通过中介者通信,而不是相互引用,所以当一个对象发生改变时,只需要通知中介者对象即可。
面向对象设计鼓励将行为分布到各个对象中,把对象划分成更小的粒度,有助于增强对象的可复用性,但由于这些细粒度对象之前的联系激增,又有可能反过来降低其可复用性。中介模式可以使各对象之间耦合松散,可以独立地改变它们之间的交互,将网状的多对多关系变成了相对简单的一对多关系。
# 模板方法模式TemplateMethod
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
模板方法模式适用于以下情况:
- 一次性实现一个算法不变的部门,并将可变的部分留给子类来实现
- 各子类中公共的行为应该被提取出来并集中到一个公用父类中以避免代码的重复
- 控制子类扩展,模板方法只在特定点调用
hook
操作,也就只允许在这些点进行扩展