设计模式
工作了也1年多了,最近2个月把那本经典的设计模式看了一遍。还是蛮有收获的。至少比以前在学校的时候看多了很多的体会。希望接下来的工作中,能够慢慢地成为自己的利器之一。这里简单的记录一下自己的读书笔记。
抽象工厂模式--对象创建型模式
意图:提供一个创建一系列相关或相互依赖对象的接口,无需指定他们具体的类。产品种类是固定的,产品系列可以被替换~想增加产品种类会比较麻烦,因为所有工厂都必须改。
就是定义了一个抽象类,这个类定义了一些接口,然后每种想创建的都是具体的子类来实现具体的功能,然后用户们完全根据抽象类提供的接口
来使用。客户完全不知道他们使用的是哪些具体类。
适用性:
- 一个系统要独立于他的产品的创建,组合和表示的时候
- 系统要由多个产品系列的一个来配置
- 强调一系列产品对象的设计以便联合使用
- 提供一个产品类库,只想显示他们的接口而不是他们的实现时
优缺点:
- 分离了具体的类:将客户与类的实现分离,客户通过他们的抽象接口操纵实例。
- 使得易于交换产品系列:具体工厂只在初始化的时候,于是我们可以很方便的改变应用的具体工厂
- 有利于产品的一致性:当系列中产品对象被设计成一起工作的时候,他能保证一次只能使用同一个系列的对象
- 不能支持新种类的产品:就是抽象工厂如果被扩展的话,会影响到所有的实现。
我们使用工厂的时候,可以通过传递一个参数来指示要创建的对象的种类的参数。
BUILDER(生成器)--对象创建型模式
意图:将一个复杂对象的构建和他的表示分离,使同样的构建过程可以创建不同的表示。不变的是构建步骤,构建的具体操作可以被多种替换~
就是用户使用一个director来根据一定的步骤来调用builder的创建方法,然后用户从builder那里得到最后生成的东西。(之所以不从director拿是因为director里面不知道生产的是什么产品,只有builder和用户才知道产品是什么)
适用性:
- 当创建复杂对象的算法应该独立于对象的组成方式以及装配方式
- 构造过程必须允许被构造的对象有不同的表示时
效果:
- 可以改变一个产品的内部表示:因为提供给导向器的是一个抽象接口,所以产品的表示和内在结构其实是被隐藏的
- 将构造代码和表示代码分开:就是代码拆离开了,用户可以使用不同的director来在相同构件的基础上构做不同的product
- 可以使得构造过程更精细的控制:因为是在导向者的控制下进行合成,所以builder的接口会很好的反映产品的构造过程
这里说抽象工厂和建造者模式的区别是:builder是一步步构造复杂对象,抽象工厂着重于多个系列的产品对象,抽象工厂直接拿到产品,builder最后拿到。
工厂方法--对象创建型模式
意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到他的子类。选择使用哪个工厂的事情交给了使用者。每一个新的产品都得新增一个工厂。
适用性:
- 当一个类不知道它所必须创建的对象的类的时候
- 当一个类希望由他的子类来指定它所创建的对象的时候
- 当类将创建对象的职责委托给多个帮助子类中的某一个
效果:
- 代码仅处理product接口,可以与用户定义的任何ConcreateProduct类一起使用
- 每一个产品都需要创建一个工厂
- 工厂方法创建比直接创建一个对象更灵活,他给了子类一个挂钩,可以提供对象的扩展版本。
- 连接平行的类层次,将信息局部化了,保持类的平行。
书里没有介绍简单工厂,就是只有一个创建的工厂,通过传入的参数的不同来创造出不同的产品,坏处是要增加新产品的时候得修改原有的工厂,违反了开放封闭原则。
PROTOTYPE(原型) --对象创建型模式
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
效果:
- 对于用户隐藏了具体的产品类
- 减少了子类的构造,省去了creator这种东西。
缺点:每个类都得实现一个clone借口
这个模式主要是GUI时代的产物,那个时候需要的就会复制一个东西,而现在这种需求很少了。前端就更少了,前端没有类的概念,复制一个对象的成本很小。
SINGLETON(单件) --对象创建型模式
意图:保证一个类只有一个实例,并且提供一个访问它的全局访问点
适用性:当类只能有一个实例并且客户可以从一个众所周知的访问点访问它的时候,类负责保存唯一实例,并且通过截取住创造函数的方式来截取住
效果:
- 对唯一实例的受控访问
- 缩小命名空间
- 允许对操作和表示的精化
- 允许可变数目的实例
- 比类操作更灵活
ADAPTER(适配器) --类对象结构性模式
适用性:
- 如果想使用一个已经存在的类,然而他的接口不符合我们的需求
- 想创建一个可以复用的类,与其他不相关的类或不可预见的类协同工作
- (对象adapter)使用已经存在的子类,但是不可能对每一个都进行子类化以匹配他们的接口。对象适配器可以适配父类接口
目的是改变已经存在的接口
结构的话:
- 类适配器就是客户端需要的一套接口,然后第三方提供了另外的接口,然后适配器就继承两个,然后在客户端需要的接口中调用第三方的接口
- 对象匹配器居室客户端需要一套接口,然后第三方提供了另外的接口,然后适配器继承客户端,然后拥有一个第三方的实例,然后在客户端需要的接口中调用第三方的接口
效果:
- 用一个具体的类来对adaptee和target进行匹配。但是如果我们想要匹配一个类以及他所有的子类的时候,不能胜任
- adapter冲定义adaptee的的部分行为
- 仅仅引入了一个对象,并不需要额外的指针以间接得到apaptee。
BRIDGE(桥接) --对象结构型模式
意图:将抽象部分与实现部分分离,使得他们都可以独立的变化
适用性:
- 不希望在抽象与实现之间有一个固定的绑定关系。可能希望实现部分可以被选择或者切换。
- 对用户隐藏抽象的实现部分
- 类的抽象和他的实现都应该可以通过生成子类的方法加以扩充。
结构:
就是抽象类内部聚合了一个具体实现类的抽象,然后在内部接口调用这个具体实现类接口的实现。然后创建的时候只要set一下内部的具体实现类就好了。想切换的时候就重新set就好了。
效果:
- 分离接口及其实现部分,实现可以在运行时进行配置,甚至动态改变。
- 提高可扩充性
- 实现细节对客户透明
COMPOSITE(组合) --对象结构型模式
意图:使得用户对于单个对象和组合对象的使用具有一致性
适用性:
- 想表示对象的部分-整体层次结构
- 希望用户忽略组合对象和单个对象的不同,用户将统一的使用组合结构中的所有对象。
结构:
就是定义一个类,既有普通的图元所拥有的操作方法,又有作为容器的那些添加,删除,获得子孙的方法,。然后叶类继承这个类,实现基本操作,容器类继承,在内部聚合叶类。
效果:
- 定义了包含基本对象和组合对象的类层次结构,支持不断的递归嵌套
- 简化客户代码,不需要知道是使用组合对象还是单个对象
DECORATOR(装饰) --对象结构型模式
意图:动态的给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更加灵活
适用性:
- 在不影响其他对象的时候,以动态,透明的方式给单个对象添加职责
- 处理那些可以撤销的职责
- 当不能使用生成子类的方法进行扩展时
结构:
就是一个装饰器类,聚合真实的组件,提供与真实组件一样的接口,然后在自己的接口中调用真实组件的接口。多种装饰器的话就都继承
效果:
- 比静态继承更加灵活,减少了类,减少了复杂度
- 与他的组件是不同的
- 不必为了不需要的特征付出代价。避免了高层的类有太多的特征
- 他的实现场景会比较适合较小的类,因为较大的类使用装饰器代价比较大,因为对外的接口必须是一致的,这个时候更适合策略模式
相关模式:
适配器模式不同之处在于他会给对象一个新的接口
FACADE(外观) --对象结构模式
意图:为子系统的一组接口提供一个一致的界面,定义了一个高层接口,接口使得子系统更容易使用。
适用性:
- 为一个复杂子系统提供一个简单接口。
- 客户程序与抽象类的实现部分分离,提高系统的独立性和可移植性。
- 当构建一个层次结构的子系统,facade模式定义每层的入口点,如果子系统相互依赖,可以让他们仅通过facade通讯,简化他们之间的依赖关系。
效果:
- 对客户屏蔽子系统组件,减少客户处理的数量使得使用更加方便
- 实现了子系统与客户之间的松耦合关系,消除复杂的依赖
facade也有点像适配器,但是facede是创造了新的接口,而适配器是使两个已有的接口协同工作
FLYWEIGHT(享元) --对象结构型
意图:运用共享技术有效支持大量细粒度的对象
注意:这里很大程度上并不是取决于创造的代价,而是调用的频率
适用性:
- 应用程序使用了大量的对象
- 大量的对象造成了很大的存储开销
- 大多数状态都可以变为外部状态
- 应用程序不依赖于对象标识
结构:
就是有个享元抽象类,然后有个存储着享元的池子。普通情况下都是直接从池子里获取享元,当不需要共享的时候。也有一个子类实现了享元接口,可以直接new出来,大部分时候都是从池子里面拿。注意享元内部的状态都是共享的状态。想要不同的地方,只能使用享元的时候从外部传入。
效果:
共享的flyweight越多,空间的节省也就越大。
PROXY(代理) --对象结构型模式
意图:对其他对象提供一种代理以控制这个对象的访问。
使用性:
- 远程调用:对象在不同的地址空间提供局部代理
- 虚代理:就是开销的问题,比如浏览器会先用一个空的图片在那边占个位置,然后慢慢下载
- 保护代理:就是对象应该有不同的访问权限的时候
实现:
就是代理类里面聚合了一个实例,然后提供了与实例一样的接口,将操作代理到实例上。
效果:
- 隐藏一个对象存在于不同地址空间的事实
- 进行最优化,根据要求创建对象,就是一开始没必要实例化,调用的时候再实例化
- 加一些其他的内务处理
其他模式:
- 与适配器的区别在于适配器提供的是不同的接口
- 与装饰器的模式实现很像,目的不同,装饰器是增加功能,代理是控制管理
CHAIN OF RESPONSIBILITY(职责链) --对象行为型模式
意图:就是一个行为沿着一个对象链冒泡,可能会被其中的某个对象处理,使得多个对象都有机会处理对象,避免了发送者和处理者之间的耦合关系。
适用性:
- 多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定
- 不确定接受者的时候,向多个对象中的一个提交一个请求
结构:就是很多个处理者,他们都拥有处理请求的方法并且可以访问到他们的上一级,一个请求从底层向上传递,可能会被其中的一个处理了或者继续向上传递。
效果:
- 降低耦合性:对象无需知道谁来处理,仅仅需要知道这个请求被正确的处理了,接受者和发送者都没有对方的明确信息
- 不保证被接受
- 增强了给对象指派职责的灵活性
总结:如果这个链经常被修改的话,是可以弄成这个模式的,不一定那种if else的就一定合适,主要还是看项目的演化方向
COMMAND(命令) --对象行为型模式
意图:将一个请求封装成一个对象,可以使用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作
适用性:
- 在不同的时刻指定,排列和执行请求
- 支持取消操作,就是command的execute可以顺便存储下状态,然后在取消操作时按照这个状态消除掉操作的影响。
- 支持修改日志,就是借口提供了添加装载操作以及存储操作,崩溃回复的时候可以拿取这些命令并且重新执行他们。
结构:就是客户端声明一些实现的命令对象,并且给他指定好接受者对象。然后发起者拥有这些命令对象。真正的触发由发起者来发起。发起者可以对拥有的这些个命令进行各种排序啊,状态的存储啊等等操作。
总结:就是将一个命令的直接执行过程拆开,分成了发起者和执行者两个角色。然后用一个命令对象来管理他们的关系。通过这个命令对象我们可以存储一些状态,通过这些个状态,我们可以一个是松耦合,一个是支持排队,取消,以及修改以及日志。
效果:
- 将调用操作的对象以及知道如何实现操作的对象解耦
- 增加新的命令很容易,因为不用改变已有的类
- 可以将多个命令组装成一个复合命令
INTERPRETER(解释器) --类行为型模式
意图:给定一个语言,定义它的文法的表示,并定义一个解释器。解释器使用该表示来解释句子。
适用性:
- 当有语言需要解释执行,并且可以将语言中的句子表示为抽象语法树的时候
结构:其实主要是在context里面有解析整个语句的过程,会使用一个栈,这个栈里会一步步的解析,最后的结果是各个解释的类的结果(其实就是生成一个抽象语法树)。最后的结果是一个嵌套了很多解释器的结果。栈其实就只有一个内容。每个解释类实现不同,但是都有同一个方法,基本会有两种解释器,一种就是值,一种就是操作。
效果:
- 易于改变和扩展文法
- 易于实现文法
- 复杂的文法难以维护。因为每一条规则至少定义了一个类
总结:使用的情况还真的很少,毕竟很少人会来写一个语法。最后就是通过遍历来生成一个抽象的语法树。
ITERATOR(迭代器) --对象行为型模式
意图: 提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露内部表示。
适用性:
- 访问一个聚合对象的内部而不暴露他的内部展示
- 支持对聚合对象的多种遍历
- 为遍历不同的集合对象提供一个统一的接口
结构:这个集合对象也是一个抽象的接口,也会有一个实现的集合对象。然后申明了一个抽象的迭代器,然后一个具体实现的迭代器(new的时候会传入一个具体的集合对象,这里切换迭代器其实是一种工厂模式)。两个抽象主要是为了遍历不同的结构和不同的遍历方法。
效果:
- 支持不同的方式遍历一个集合
- 简化了聚合的接口
- 在同一个聚合上可以有多个遍历
MEDIATOR(中介者) --对象行为型模式
意图:用一个中介对象来封装一系列的对象交互,使得各对象不需要显示的相互引用。从而松散耦合。而且可以独立的改变他们之间的交互。
适用性:
- 一组对象以定义良好但是复杂的方式通信,相互依赖的结构混乱且难以理解
- 一个对象引用很多对象并且直接与这些对象通信,导致难以复用这个对象
- 想定制一个分布在多个类之间的行为。而不像生成太多的子类
结构:就是定义了一个同事的抽象,然后每个实现的子类的实例拥有中介者的引用。每个指令发出去都是通过中介者来发。中介者拥有着这些个实例的所有的引用。他会针对发出的请求的实例做不同的事情。
效果:
- 减少了子类生成
- 将各个colleague解耦
- 简化了对象协议,把多对多的关系,变成了中介者与各个实例间一对多的关系
- 使得控制集中化,但是中介者本身可能会变得无法维护
MEMENTO(备忘录) --对象行为型模式
意图:不破坏封装性的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态以便恢复。
适用性:
- 必须保存一个对象在某个时刻的状态,以后需要他的时候才能恢复到它的状态。
- 如果用接口让其他对象能访问到这些状态,会暴露对象的实现细节并破坏对象的封装性
结构:就是发起者负责根据自身状态创建备忘录对象(他可以通过备忘录来进行自身的恢复工作)。然后外部还有个负责人来保存这个备忘录的引用,提供get,set方法。发起者再从这个负责人这里拿取来恢复。
效果:
- 保持封装边界
- 简化了原发器
- 使用备忘录的代价可能会比较高
- 维护备忘录的代价可能不小
OBSERVER(观察者) --对象行为型模式
意图:定义对象之间的一对多的依赖关系,当一个对象状态发生改变的时候,依赖的对象得到通知并自动更新
适用性:
- 当一个对象的改变需要通知多个对象,而且不知道具体有多少
- 不希望对象之间的紧耦合
结构:就是一个抽象对象类,里面有添加和删除观察者的方法,还有通知的方法。具体的对象实现这个类,然后保存有自己内部的状态。有个观察者的抽象,里面只提供了update方法,然后具体的观察者实现抽象类并存有内部的状态。
效果:
- 目标和观察者之间的解耦(这种解耦一般是由于处在不同的层级上,所以需要解耦)
- 支持广播通信
- 可能导致意外的更新
总结:其实我们js中大部分的观察者都是推模式,但是真正的观察者其实是拉模式。
STATE(状态) --对象行为型模式
意图:允许一个对象在他的内部状态改变时改变他的行为。
适用性:
- 当一个对象的行为取决于他的状态。并且会在运行时刻根据状态改变它的行为。
- 拥有庞大的多分支的条件语句。且这些分支依赖于该对象的状态。
结构:就是一个context内部聚合了一个state的实例。state定义了context的特定的状态的行为。注意每个实现了state的类都要把这些个行为实现。然后通过切换状态然后重新调用方法来实现。
效果:
- 将与特定状态相关的行为局部化
- 使得状态转化显示化
- state状态可以被共享。这个点和享元模式就有点关系了。
总结:这个模式其实和职责链有些相似,当初我也觉得职责链能够解决多个if的场景,后来发现状态模式更适合。因为状态是单个对象的状态的切换,而职责链是多个对象之间的切换。而且职责链其实是switch。而状态是if else。这个模式和享元可以配合。因为他的state是独立的。注意状态这个概念是持久化的,才能叫状态。必须是严格的显示依赖于状态才行。
STRATEGY(策略) --对象行为型模式
意图:定义一系列的算法,把他们一个个封装起来,使得他们可以相互替换。使得算法可独立于使用它的客户而变化。
适用性:
- 许多相关的类仅仅是行为有异
- 需要使用一个算法的不同变体
- 算法使用用户不应该知道的数据
结构:很简单,就是一个上下文聚合了一个抽象的算法。这个算法会有很多种实现。
效果:
- 定义了一系列的可供重用的算法或者行为
- 替换了继承的能力
- 消除了一些条件语句
总结:这个玩意和桥接模式很像,但是区别还是有的,首先策略是行为模式,而桥接是结构性模式。桥接的的调用方是可以有自己的变化的,而策略模式不能有自己的变化。桥接模式更多的是体系的隔离。可以说桥接模式包含着策略模式。但是桥接模式比策略更加的高级一些。更多的是一种接口隔离的原则,使之可以松散的耦合。主要是看一个问题的角度问题
TEMPLATE METHOD(模板方法) --类行为型模式
意图:定义一个操作算法的骨架,将一些步骤延迟到子类。是的不改变算法的结构就可以重新定义特定步骤
适用性:
- 一次性实现不变的部分,将可变的行为留给子类来实现
- 各子类中公共行为被提取出来集中到公共父类来避免代码重复
- 控制子类扩展(就是只提供了特定的口子来让子类扩展)
结构:就是声明一个模板类,大部分的结构都是已经实现好了的,一些特定的步骤用虚方法来写,然后子类其实只要实现虚方法就好了
效果:
- 模板方法提供一个反向的控制结构,通过一个父类来调用一个子类的操作。这点很神奇啊(钩子操作)
- 提取了公共行为来代码复用。
总结:其实模板方法的运用就是提供了钩子函数,在定义了具体的执行顺序(也就是模板)的时候,对外提供了在特定的点执行的能力的钩子函数。
VISITOR(访问者) --对象行为型模式
意图:表示一个作用于对象结构的个元素的操作。使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
适用性:
- 一个对象结构包含很多类对象。包含了不同的接口,想对这些对象实施依赖于具体类的操作
- 对一个对象结构进行很多不同的且不相关的操作。想避免这些操作污染对象的类。
- 定义对象结构的类很少改变,但是需要在这个结构上定义新的操作。
结构:就是一个对象结构内部有多个对象。访问内部的这些对象的时候。传入一个访问者对象。然后调用特定的方法的时候,把自身传进去。然后在访问者对象里面根据传入的是谁来决定相应的操作
效果:
- 访问者模式易于增加新的操作
- 集中相关的操作而分离无关的操作
- 通过类层次进行访问
- 可能会破坏封装,因为对外提供了内部状态的公共操作
总结
个人感觉设计模式其实首先得有后续的代码的变化。其次要有一定的复杂度。一般都是将变化隔离开。或者将复杂的地方,包括算法(策略模式),多个对象之间的联系(中介者模式)等等进行一定的隔离。
文章可以直接访问我的前端网站来查看,平时的日常整理也都会记录上去。