23种JavaScript设计模式
1.为什么要学习设计模式?
在许多访谈中,你可能会遇到很多面向对象编程中的接口,抽象类,代理和以及其他与设计模式相关的问题。 一旦了解了设计模式,它会让你轻松应对任何访谈,并可以在你的项目中应用这些特性。在应用程序中实现设计模式已经得到验证和测试。
为了使应用程序具有可扩展性,可靠性和易维护性,应该编写符合设计模式的代码。
2.什么是设计模式。
设计模式是我们每天编程遇到的问题的可重用解决方案。
设计模式主要是为了解决对象的生成和整合问题。
换句话说,设计模式可以作为可应用于现实世界编程问题的模板。
3.设计模式的发展历史
设计模式的概念是由四人帮(《设计模式(可复用面向对象软件的基础)》的四位作者)提出。
四人帮把这本书分成两部分:
第一部分解释面向对象编程的优缺点。
第二部分是关于 23 个经典设计模式的演变。
自提出设计模式概念后,四人帮设计模式在软件开发生命周期中发挥了重要作用。
4.设计模式分类
根据实际应用中遇到的不同问题,四人帮将设计模式分为三种类型。
创建型模式
结构型模式
行为型模式
接下来将概述属于这三种类型的 23 种设计模式的主要概念。
4.1创建型模式
这类模式用于对象的生成和生命周期的管理。
创造模式可以决定生成哪些对象,提高了程序的灵活性。模式如下:
抽象工厂模式
生成器模式
工厂方法模式
单例模式
原型模式
4.1.1JavaScript 抽象工厂模式
# 抽象工厂模式究竟是什么?
它就像一个工厂,但一切都被封装起来:
提供对象的方法
构建对象的工厂
最终的对象
最终对象包含使用策略模式的对象
策略模式只是使用组合的方式,换句话说,它的类字段实际上是对象本身。
# 抽象工厂的用处?
抽象工厂模式可以创建类簇类的对象,而不需要指定具体的类,这使得抽象工厂很灵活。
通过抽象工厂模式可以对任何类簇对象进行建模并通过统一的接口供外部对象使用。
抽象工厂唯一不好的地方是它可能变得非常复杂。
4.1.2JavaScript 的生成器模式(Builder 模式)
#Builder 模式是什么?
Builder 模式是一种用于创建由其他对象组合构成的对象的模式。创建部件的方法应该独立于主对象。另外,为了从业务方隐藏部件的创建细节,两者是相互独立的。
在使用生成器模式时,生成器知道所有的细节,且创建细节完全对其他相关类屏蔽。
4.1.3JavaScript 工厂方法模式
# 什么是工厂方法模式?
根据定义,只要想一个方法返回公共超类的几个可能的类中的一个,就可以使用工厂模式。
假设我想随机向屏幕上射击敌人。那么如果所有东西都硬编码好了,并且不知道敌人的具体类型会让处理变得很困难。
但是,假设我创建了一个随机数生成器,每个可能的敌人类都由一个数字代替,并可以由该随机数生成器返回。
然后可以将这个数字发送给工厂对象,这样可以返回一个动态创建的敌人。
所以,MOL 的主要概念非常重要,我们希望能够在运行时选择类,这就是工厂模式能够提供的。
#何时使用工厂方法模式?
当不知道需要何种类型的对象时可以使用工厂方法模式。
但是,有一点需要注意,确保所有潜在的类都具有相同的子类层次结构,这意味着在继承路径上有相同的父类。
可以使用工厂模式来集中类别选择的代码。或是不希望用户知道每一个可能的子类时。
4.1.4JavaScript 单例模式
# 什么是单例模式?
当想要避免实例化多个对象时使用单例模式。单例使得只能从类实例化一个对象。
现在你可能会问自己,我什么时候会想要这样做? 其实,有很多场景。
我们已经演示过拼字游戏里使用一个类容纳所有可能的字母,并且拼字游戏是一款非常常见的棋盘游戏,这里使用单例模式是很明智的选择。
这个单例类包含所有可能的拼字游戏字母,以便玩家可以使用这个单例类取得所有字母,不同玩家可以同时请求获取。
单例使得每个玩家都共享相同的字母列表,每个玩家可以根据这个字母列表来拼接自己的单词。
4.1.5JavaScript 原型模式
#什么是原型模式?
当想要通过克隆或拷贝对象来生成对象时,这就是原型模式。
通过原型模式可以在运行时添加已知父类的子类实例。
当有许多类只在运行时需要使用时可以使用原型模式。原型模式的好处之一是减少了创建多个子类。
4.2结构型模式
这类模式描述了向现有对象添加功能的不同方式简单地说,这个模式着重于解耦对象的接口实现模式如下:
适配器模式
桥接模式
组合模式
装饰者模式
外观模式
享元模式
代理模式
4.2.1JavaScript 适配器设计模式
#适配器模式的作用
适配器设计模式允许使用两个完全不兼容的接口一起工作,正如其名字一样,适配不同接口。
假设你墙上的插座只有两座插头,但是你想连接一个三座的插头,这时就需要适配器了。
当客户期望使用目标接口是两座插头,但是你只有三座插头,这就是适配器将要执行的操作 把三座转换成两座。
适配器模式允许使用任何现有接口适配为目标接口。
从另一个角度看任何类都可以协同工作只要适配器解决了所有类都必须实现相同接口的问题。
4.2.2JavaScript 桥接模式
#什么是桥接模式?
官方定义是将抽象与其实现分离开来,因此两者可以独立变化。
该模式用于将抽象与其实现分开,以便两者都可以独立修改。该模式包含一个用于桥接抽象类和实现类的接口。通过桥接模式,两种类型都可以修改而不会相互影响。
#桥接模式实现指南
当想要避免抽象与其实现之间的永久绑定时可以使用桥接模式。
抽象和它们的实现都应该可以通过子类进行扩展。当抽象的实现有变化时不应该影响到调用方,及调用方不需要重新编译。
当需要在多个对象之间共享一个实现时,可以选择这种模式。
最后我们希望完全对调用方隐藏抽象的实现。
4.2.3JavaScript 组合模式
组合模式从定义上看起来很复杂,一旦用起来就会感觉很合理。
它允许统一处理单个对象和对象组合,这是组合设计模式的典型定义。
组合模式可以表示为部分 - 整体的层次结构。该结构的组件又可以划分为更小的组件。
一个更合乎逻辑的定义是组合设计模式是用来结构化数据或单独表示整个对象的每个部分的互相操作。
4.2.4JavaScript 装饰模式
#什么是装饰模式?
动态的为一个对象附加额外的功能,装饰模式可以不通过继承来实现功能扩展。
这种模式属于结构设计模式类别,也被称为包装模式,装饰设计模式解决了在不改变对象现有结构的情况下添加附加功能的问题。
另外,该模式创建了一个装饰器类,它包装原始类并在运行时向对象添加新的行为或操作。
# 装饰模式的实现指南
当选择这种模式时,需要动态地向单个对象添加新的功能,并且而不会影响其他对象。
例如,在有些情况下,为原有代码添加新的功能可能会很困难,用修饰器修改代码会更容易些。
通过继承来实现功能扩展会产生大量的子类,并且有可能这些子类还不足以覆盖所有需要扩展的功能。
当无法查看类定义或无法继承时,需要选择装饰模式。
例如,尽管类被封装起来无法修改,通过装饰模式仍然可以进行扩展,并且,我相信你肯定也遇到过无法通过继承来实现功能扩展的情况,在这些情况下,选择装饰模式。
4.2.5JavaScript 外观模式
#什么是外观模式?
为子系统中的一组接口提供统一接口。外观模式定义了一个更高层次的接口,使子系统更易于使用。术语外观来自法语 façade,意思是正面或脸。
简单来说是外观模式隐藏了子系统的实现复杂性,为我们提供了一个简洁的接口。该接口负责调用现有子系统的功能。
#外观模式实现指南
当我们想为子系统提供一个简单的接口时需要使用外观模式。随着子系统的发展,子系统往往会变得更加复杂。
当我们在应用一些模式时,大多数模式会导致更多的类创建出来,这使得子系统可用性更好,更容易自定义需要的功能,但对于不太需要自定义的业务方而言,它也变得更加困难。
外观模式将子系统包装成一个简单默认的接口,足以应付大多数情况的使用。
另外,当业务方和子系统的实现存在过多依赖时也可以选择外观模式。
在这种情况下,外观模式将子系统从业务方和其他子系统中分离出来,从而提高子系统的独立性和可移植性。
到目前为止,如果子系统过于耦合,那么可以通过外观模式提供的接口来简化子系统之间的依赖。
4.2.6JavaScript 享元模式
#什么是享元模式?
当需要创建大量相似对象时需要使用享元模式,这里的大量是上万的量级而不是平时接触的上百。
享元模式通过共享对象的相似部分,避免重复创建,来达到减小内存的使用。
4.2.7JavaScript 代理模式
# 什么是代理设计模式?
代理是一个将被用来限制访问另一个类的类。
这可能是出于安全的考虑,通过代理来决定需要代理的对象哪些方法是可用的。
4.3行为型模式
这类模式描述了对象如何相互作用,模式如下:
责任链模式
命令模式
解释器模式
迭代器模式
中介者模式
备忘录模式
观察者模式
状态模式
策略模式
模板方法模式
访问者模式
4.3.1JavaScript 责任链模式
#什么是责任链模式?
责任链模式有一组对象,期望它们之间能够解决问题,如果第一个对象无法解决它,则将数据传递给责任链中的下一个数据。
通过多个接收者对象来处理请求,避免将请求的发送者耦合到其接收者。将接收对象串联起来,传递请求直到被其中一个处理掉。
责任链模式为请求创建一系列接收者对象。 在这种模式下,通常每个接收器都包含对另一个接收器的引用。
如果一个接收者不能处理该请求,则它将请求传递给下一个接受者。
4.3.2JavaScript 命令模式
#什么是命令模式?
命令设计模式是一种行为设计模式,其对象用于表示和封装稍后调用某个方法所需的所有信息。该信息包括方法名称,拥有方法的对象和方法参数的值。
基本上,它允许你做的是存储代码清单,在稍后或多次执行,并且通常使用命令模式可以撤销命令。
当这些封装的对象调用 execute() 方法,业务方或程序会执行指定命令或一段代码。
然后,调用者对象在调用时会传递命令给命令接收者,命令接收者拥有实际想要执行的代码,一旦从调用者接收到命令就立马执行。
例如有个"TurnTVOn”的命令对象,然后一个“DeviceButton”的对象,一旦"DeviceButton"对象被调用就会发送"TurnTVOn"命令。所有这些之间的接口可以使用多态,所以无论何时调用 TurnTVOn,该方法将由命令接收者执行。
4.3.3JavaScript 解释器模式
# 什么是解释器模式?
解释器模式很容易被忽略,并且网上很少有使用这种模式。
但是,如果与 JavaScript 反射技术结合使用,会变得非常有用。它用于将数据的一种表示转换为另一种表示。
4.3.4JavaScript 迭代器模式
# 什么是迭代器模式?
迭代器模式提供一种统一的方式来访问不同类型的对象集合。
例如,Array,ArrayList 和 HashTable,这三个集合包含相同类型的对象,通过迭代器可以取到集合里的每一个元素,并做相同的处理。
迭代器模式做的是提供统一的方式来循环遍历这些不同类型的集合。
4.3.5JavaScript 中介者计模式
#什么是中介者模式?
用于处理相关对象之间的通信。所有通信由中介者完成,通信双方不需要了解对方的任何信息。
更严格的定义是,中介者模式允许通过封装不同对象之间相互作用和相互通信的方式来实现松耦合,并且中介者模式允许每个对象的行为彼此独立地变化。
4.3.6JavaScript 的备忘录模式
#什么是备忘录模式?
用于存储对象以前状态的模式,首先,备忘录模式需要一个备忘录对象用于存储对象的不同状态,不同的状态及一些字段拥有不同的值。
然后,用于从当前目标备忘录对象读写值的初始对象,创建新的备忘录对象并赋值给当前备忘录对象。
最后,内部是一个 ArrayList 对象用于持有之前所有的备忘录对象,该 ArrayList 对象同时用于存储和检索备忘录对象。
4.3.7JavaScript 观察者模式
# 什么时候使用观察者模式?
当另一个对象更改时需要其他对象接收更新。例如,假设我们有一个对象或发布者代表股票市场中的数千个股票,当需要将更新发送给多个订阅者时,可以使用观察者模式来完成。
#观察者模式的好处
松耦合。 对象或发布者完全不用知道观察者或订阅者的存在,我把这分为两种不同的概念,因为我觉得发布 - 订阅更有意义,但 OLP 的基本术语使用的是对象 - 观察者。
#坏处
观察者模式唯一的缺点是对象或发布者可能发送对观察者,订阅者无关紧要的更新。
4.3.8JavaScript 状态模式
#什么是状态模式?
它允许对象在其内部状态改变时改变其行为,这样让对象看起来改变了它所属的类。
状态模式主要分为三个部分:
首先,将拥有所谓的“上下文”或“账户”,它将要做的是维护一个将定义当前状态的 ConcreteState 子类的实例。
然后,拥有“状态”,该状态定义了一个用于封装与上下文特定状态关联的行为接口。
最后是具体的状态,每个子类将实现与上下文状态相关的行为。
4.3.9JavaScript 策略模式
# 何时使用策略模式?
如果要定义一个类,该类将具有与列表中的所有其他行为相似的行为,可以使用策略模式。
例如,有的动物可以飞,有的动物不能飞,这里的相似行为就是飞行,不论该动物是否能飞行。
可以从以下类中选择类对象:
不会飞行
用翅膀飞翔
飞得快
通过策略模式可以动态的创建全新的不同类型的飞行类动物。
当需要动态决定需要使用的行为时可以使用策略模式。
# 使用策略模式的其他好处
它通常会减少很长的条件列表,所以如果你看到使用许多不同类型的条件时,策略模式会很有帮助。当然,还避免了重复的代码。
策略模式可以防止其他类的变化影响到当前类。也可以向业务方隐藏复杂和敏感代码。
坏处:策略模式会增加对象和类的数量。
4.3.10JavaScript 模板方法模式
#什么是模板方法模式?
用于创建一组执行类似方法的子类。
要实现它,需要创建一个抽象类,它将包含一个名为模板方法的方法。
创建包含称为模板方法的抽象类,并且模板方法包含每个子类对象将调用的一系列方法调用。在某些情况下子类也会重载一些不合适的方法调用。
4.3.11JavaScript 访问者模式
# 什么是访问者模式?
访问者模式允许将方法添加到不同类型的类中 - 但它们不必是不同的类型 - 只会让复杂度有所增加,并不会对类有太多修改。
这可以根据所使用的类制作完全不同的方法。
也可以说访问者模式可以为现有类创建外部类来进行扩展以避免对原有类做修改。
注意:掌握抽象,继承,多态,封装,接口,类和抽象类等面向对象概念的基本知识对于更好地理解设计模式非常重要。