1

大纲

创造性模式

  • 工厂方法模式创建对象而不指定要创建的确切类。
  • 抽象工厂模式将具有共同主题的对象工厂分组。
  • Builder模式通过分离构造和表示来构造复杂的对象。

结构模式

  • Bridge将抽象从其实现中分离出来,这样两者可以独立变化。
  • 代理提供另一个对象的占位符来控制访问,降低成本并降低复杂性。
  • 复合材料组成零个或多个相似的对象,以便它们可以作为一个对象进行操作。

*行为模式

  • 中介者可以通过成为唯一具有其方法详细知识的课程,从而实现课堂间的松散耦合。
  • Observer是一种发布/订阅模式,它允许许多观察者对象查看事件。
  • 访问者通过将方法的层次结构移动到一个对象中来将算法从对象结构中分离出来。
  • 责任链将命令委托给一系列处理对象。
  • 命令创建封装动作和参数的对象。

对可重用性和可维护性设计模式的高层考虑

创造性模式

(1) Factory Method pattern

工厂方法模式
也称为“虚拟构造器”

意图:

  • 定义一个用于创建对象的接口,但让子类决定实例化哪个类。
  • 工厂方法让类将实例化推迟到子类。

什么时候应该使用工厂方法? ----当一个类:

  • 无法预测它需要创建的对象的类别
  • 希望它的子类指定它创建的对象
  • 将责任委托给多个帮助者子类中的一个,并且您需要本地化哪些帮助者是委托人的知识。

当客户不知道要创建哪个具体类的实例,或者不想在客户代码中指明具体创建的实例时,用工厂方法。定义一个用于创建对象的接口,让其子类来决定实例化哪一个类 ,从而使一个类的实例化延迟到其子类。

优点:

  • 消除了将特定于应用程序的类绑定到代码的需要。
  • 代码只处理产品接口(Trace),因此它可以与任何用户定义的ConcreteProduct(FileTrace,SystemTrace)

潜在的缺点

  • 客户可能必须创建Creator的子类,以便他们可以创建某个ConcreteProduct。
  • 如果客户无论如何都要继承创造者的话,这是可以接受的,但如果不是,那么客户必须处理另一个进化点。

Open-Closed Principle(OCP) - 对扩展的开放,对修改已有代码的封闭

(2) Abstract Factory

抽象工厂模式

示例1:考虑一个用户界面工具包,它支持不同操作系统的多个外观和感觉标准:一个UI,包含多个窗口控件,这些控件在不同的OS中实现不同

  • 如何编写单个用户界面,并使这些界面在这些窗口管理器的不同外观和感觉标准之间移植?

示例2:考虑支持不同控制系统的智能住宅的设施管理系统:一个仓库类,要控制多个设备,这些设备的制造商各有不同,控制接口有差异

  • 如何编写独立于制造商的单个控制系统?

抽象工厂模式:提供接口以创建一组相关/相互依赖的对象,但不需要指明其具体类。

名称:抽象工厂(或工具包)
意图:允许独立于实现创建相关对象的家族
方法:使用工厂返回可用于创建相关对象集的工厂。

适用性

  • 独立于初始化或表示的不同系列组件(产品)
  • 必须以互斥和一致的方式使用
  • 隐藏来自客户的多个家庭的存在
  • 制造商独立
  • 应对即将到来的变化

笔记

Abstract Factory创建的不是一个完整产品,而是“遵循固定搭配规则的多类产品的实例”,得到的结果是:多个不同产品的对象,各产品创建过程对客户可见,但“搭配”不能改变。
本质上,Abstract Factory是把多类产品的工厂方法组合在一起

例如

GraphPoet由Word列表和WordNeighborhood列表组成
NetworkTopology由计算机/服务器/路由器列表和NetworkConnection列表组成
换句话说,Vertex和Edge的对象创建密切相关,但不应该是独立的。 Vertex和Edge的子类,要有固定搭配,不能随意组合

抽象工厂vs工厂方法

工厂方法仅用于创建一个产品,但抽象工厂用于创建相关或依赖产品系列。

  • 创建一个对象vs创建多个类型的对象

工厂方法模式向客户端公开创建对象的方法,而抽象工厂则显示由这些工厂方法组成的一系列相关对象。

  • 一个工厂方法vs多个工厂方法

抽象工厂模式使用组合来将创建对象的责任委派给另一个类,而工厂方法模式使用继承并依赖于派生类或子类来创建对象。

  • 使用组合/委派vs使用继承/子类型

(3) Builder

构造器模式

构造器模式:将复杂对象的构造与其表示分开,以便相同的构建过程可以创建不同的表示。创建复杂对象,包含多个组成部分

  • 复杂对象的构建在多个表示中很常见

示例:将文档转换为多种不同的格式

  • 写出文件的步骤是相同的
  • 每一步的细节取决于格式

就像你在麦当劳点餐一样!

途径

  • 构建算法由单个类(“导演”)指定
  • 算法的抽象步骤(每个部分一个)由接口(“构造器”)指定
  • 每个表示都提供了界面的具体实现(“实际构造者”)

注意:客户端要的不是一堆零散的对象(抽象工厂那样的结果),而是一个完整的产品,客户端不关心其中的细节组成部分是什么,如何创建

比较:抽象工厂vs构造器

Abstract Factory创建的不是一个完整产品,而是“遵循固定搭配规则的多类产品实例”,得到的结果是:多个不同产品的实例对象,各产品创建过程对客户可见,但“搭配”不能改变。

  • 专注于产品系列(类似的产品类型)
  • 不隐藏构造过程

Builder创建的是一个完整的产品,有多个部分组成,客户不需要了解每个部分是怎么创建,各个部分怎么组合,最终得到一个产品的完整对象

  • 基础产品需要构建为系统的一部分,但创建非常复杂(产品组合)
  • 复杂产品的构造会不时变化
  • 隐藏用户的创建过程

抽象工厂和构造器可以很好地协同工作,适用于多种复杂产品系列

例如

GraphPoet由Word列表和WordNeighborhood列表组成
NetworkTopology由计算机/服务器/路由器列表和NetworkConnection列表组成
换句话说,ConcreteGraph的对象创建由Vertex和Edge子类型的对象创建组成。 四个图应用都要创建图对象,要为不同应用建立出不同的顶点列表和边列表。

比较:模板方法vs构造器

模板方法:行为模式,目标是为了复用算法的公共结构(次序)

  • 定义了一个操作中算法的骨架(步),而将具体步骤的实现延迟到子类中,从而复用算法的结构并可重新定义算法的某些特定步骤的实现逻辑。
  • 复用算法骨架,强调步骤的次序 - 子类override算法步骤

Builder:一种创造模式,目标是“创建复杂对象”,灵活扩展

  • 将一个复杂对象的构造方法与对象内部的具体表示分离出来,同样的构造方法可以建立不同的表现。
  • 不强调复杂对象内部各部分的“次序”
  • 子类override复杂对象内部各部分的“创建”
  • 适应变化:通过派生新的builder来构造新的对象(即新的内部表示),OCP

结构模式

(1) Bridge

桥接模式

适用性

  • 用不同的实现去耦合抽象概念
  • 实施可能会在运行时切换
  • 实施变更不应影响客户
  • 从客户端隐藏类的界面

结构:使用两个层次结构

  • 适合客户的逻辑
  • 为不同的实现物理一个

对象:提高可扩展性

  • 逻辑类和物理类独立变化
  • 隐藏来自客户的实施细节

桥接模式是OOP最基本的结构模式,通过委托+继承建立两个具体类之间的关系(DIP依赖转置,抽象依赖于抽象)

桥接模式与策略模式

桥接:结构模式,强调双方的运行时委派连接

  • 结构模式表明对象组成或关联或继承以形成更大对象的方式,即它们关注对象组成。

一个类A的对象中有其他类B的对象作为其组成部分,但A的对象具体绑定到B的哪个具体子类的实现?在运行时通过委托加以组合,并永久保存这种代理关系。

  • 它将抽象和实现分离开来,并允许两者独立地变化。

策略:行为模式,强调一方运行时使用另一方的“算法”

  • 行为模式处理算法或业务逻辑(而不是对象创建本身),即它们专注于对象之间的协作。
  • 它使您能够在运行时在一系列算法中切换多种算法。

“算法”通常实现为“类的某个方法”的形式,策略模式的目的并非在“调用算法的类”与“被调用算法所在的类”之间建立起永久联系,而只是帮助前者临时使用后者中的“算法”,前者无需永久保存后者的实例。

策略:使用螺丝刀的时候,针对不同的工作任务,选取不同的“刀头”,但目的并非将螺丝刀与刀头组合起来建立永久的团队,而只是临时通过委派完成任务(即调用刀头的“算法”),然后二者再无联系。

桥接:类A需要完成“通讯”功能,它有一个组成部分Device类,这是个抽象接口(模式中的实现者),有多种实现方式(PC,tablet,phone,即ConcreteImplementor),该模式在运行时为类A的具体子类型实例设定具体的Device的具体实现(强调对A和Device两个抽象类的各自实例之间如何建立委托),进而在其他各项功能中利用其通讯功能(这不再是桥接模式关注的目标)。

(2) Proxy

代理模式

代理模式动机

目标:

  • 防止客户直接访问对象
  • 通过充当传递实体或占位符对象来允许进行对象级访问控制。

解决方案:

  • 使用一个称为代理的附加对象
  • 客户端只能通过代理访问受保护的对象
  • 代理跟踪受保护对象的状态和/或位置

某个对象比较“敏感”/“私密”/“贵重”,不希望被客户端直接访问到,故设置代理,在二者之间建立防火墙。

代理模式:3种类型

信息缓存(“远程代理”)

  • 代理对象是不同地址空间中对象的本地代理
  • 如果信息没有经常变化,那么很好

Standin(“虚拟代理”)

  • 对象创建成本太高或者下载成本太高。
  • 如果真正的对象不被太频繁地访问,那么很好

访问控制(“保护代理”)

  • 代理对象为真实对象提供保护
  • 好的时候,不同的演员应该对同一个对象有不同的访问和查看权限
  • 示例:管理员,教师和学生访问的成绩信息。

代理与适配器

适配器:结构模式,目的是将类/库A的接口更改为客户端B的期望。
目的:消除不兼容,目的是B以客户端期望的统一的方式与A建立起联系。

  • 典型的实现是一个包装类或一组类。
  • 目的不是为了方便将来的界面更改,而是当前的界面不兼容。

代理:行为模式,也使用包装类,但其目的是为实际资源创建一个替身。
目的:隔离对复杂对象的访问,降低难度/代价,定位在“访问/使用行为”

  • 真正的资源驻留在远程计算机上(代理方便与远程资源的交互)
  • 真正的资源创建成本很高(代理确保成本不会发生,除非/直到真正需要)
  • 代理提供了替代它所代表的真实资源的插入替代品,因此它必须提供相同的接口。

3) Composite

组合模式

问题:

  • 应用程序需要操作“原始”和“复合”对象的分层集合。
  • 原始对象的处理是单向处理的,处理复合对象的处理方式不同。
  • 在尝试处理每个对象之前必须查询每个对象的“类型”是不可取的。

意图:将对象组成树结构以表示整体部分层次结构。

  • 组合模式让客户可以统一处理单个物体和物体的组合。
  • 递归组合
  • “目录包含条目,每个条目都可以是目录。”
  • 一对多“has a”之上“is a”层次

组合策略

包含菜单项的菜单,每个菜单项都可以是菜单。
包含小部件的行列GUI布局管理器,每个小部件都可以是行列GUI布局管理器。
包含文件的目录,每个文件都可以是一个目录。
包含元素的容器,其中每个容器都可以是容器。
树结构

复合vs装饰器

复合:结构模式,允许您以允许外部代码将整个结构视为单个实体的方式构建分层结构(树)。目的是在同类型的对象之间建立起树型层次结构,一个上层对象可包含多个下层对象

  • 叶实体的接口与复合实体的实体完全相同。
  • 复合结构中的所有元素都具有相同的界面,即使有些元素是叶节点,而其他元素是整个结构。

装饰器:结构模式,也允许一个实体完全包含另一个实体,以便使用装饰器看起来与包含的实体相同。强调的是同类型对象之间的“特性增加”问题,它们之间是平等的,区别在于“拥有特性”的多少,每次装饰只能作用于一个对象。

  • 这允许修饰器修改其封装的行为或内容,而不改变实体的外观。

行为模式

(1) Observer

观察者模式

问题:依赖的状态必须与主人的状态一致

解决方案:定义四种对象:

  • 摘要主题:保留家属名单; 当主人更改时通知他们
  • 抽象观察者:定义更新依赖者的协议
  • 具体主题:为受抚养人管理数据; 当主人更改时通知他们
  • 具体的观察者:在收到更新消息后获得新的主体状态

“粉丝”对“偶像”感兴趣,希望随时得知偶像的一举一动
粉丝到偶像那里注册,偶像一旦有新闻发生,就推送给已注册的粉丝(回调粉丝的特定功能)

建模对象之间的一对多依赖关系

  • 连接观察对象的状态,主体与许多观察对象,观察者

用法:

  • 保持冗余状态的一致性
  • 优化一批更改以保持一致性

保持一致性的三个变体:

  • 推送通知:每当主题状态改变时,所有观察者都会收到通知
  • 推送更新通知:主题还将已更改的状态发送给观察者
  • 拉通知:观察者询问对象的状态

也称为发布 - 订阅(Publish-ubscribe)。

优点:

  • 主体和观察者之间的低耦合:主体不知道家属
  • 支持广播:动态添加和删除观察员
  • 意外更新:由观察者在计算上不受主题的控制

实施问题

  • 存储观察员列表:通常在主题中
  • 观察多个主题:通常将参数添加到update()
  • 谁触发更新:主体的状态设置操作

(2) Visitor

访客模式

访问者模式:允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离。 对特定类型的对象的特定操作(访问),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被访问的类

  • Visitor模式实际上做的是创建一个使用其他类中的数据的外部类。
  • 如果操作逻辑改变了,那么我们只需要在访问者实现中进行更改,而不是在所有项目类中进行更改。

本质上:将数据和作用于数据上的某种/些特定操作分离开来。

访客与迭代器

迭代器:行为模式,用于顺序访问聚集,而不暴露其基础表示。 所以你可以在一个Iterator后面隐藏一个List或数组或类似的聚合。
迭代器:以遍历的方式访问集合数据而无需暴露其内部表示,将“遍历”这项功能委托到外部的迭代器对象。

访问者:行为模式,用于对元素结构执行操作而不改变元素本身的实现。
在特定ADT上执行某种特定操作,但该操作不在ADT内部实现,而是委托到独立的访客对象,客户端可灵活扩展/改变访问者的操作算法,而不影响ADT

策略vs访客

访客:行为模式
策略:行为模式

二者都是通过授权建立两个对象的动态联系

  • 但是Visitor强调是的外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept 访客)即可,客户端通过它设定访客操作并在外部调用。
  • 而策略是强调是对ADT内部某些要实现的功能的相应算法的灵活替换。这些算法是ADT功能的重要组成部分,只不过是委托到外部战略类而已。

区别:访客是站在外部客户的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),策略是站在内部ADT的角度,灵活变化对其内部功能的不同配置。

(3) Mediator

中介模式

使用中央控制器是中介模式的关键方面。

  • 机场控制塔照看可以起飞的航班,并且所有的通信都是从飞机到控制塔完成的,而不是进行飞机与飞机之间的通信。 多个对象之间要进行交互,不是直接交互,而是通过mediator,实现消息的广播,从而将对象之间松散耦合,利于变化

通过封装不同对象集相互交互和通信的方式,允许松耦合。

  • 允许每个对象的操作彼此独立地变化。

观察者模式与中介模式

观察者模式:定义对象之间的一对多依赖关系,以便当一个对象改变状态时,它的所有依赖项都会被自动通知和更新。
一组对象对另一个对象B的状态变化感兴趣(1对多),所以对其进行观察.B维持着一个对自己感兴趣的对象列表,一旦自己发生变化,就通知这些对象。并不对等,一方只是“通知”,另一方接到通知后执行特定行为。

中介模式:定义一个封装一组对象如何交互的对象。介体通过让对象明确地互相引用来促进松散耦合,并且可以让您独立地改变它们的相互作用。
一组同类型的对象,彼此之间相互发/收消息(多对多),不存在谁观察谁的问题,所有对象都对等。每个对象都维持一个中介,将通讯的任务委托给它,自己只负责发送和接收即可,无需了解其他物体,实现了“解耦”。

观察者模式:自己来广播,其他对象接收;
中介模式:第三方中介负责广播(类似于“邮件列表”)。

(4) Command

命令

意图

  • 将请求封装为一个对象,从而让您用不同的请求参数化客户端,排队或记录请求,并支持可撤销操作。

将“指令”封装为对象,指令的所有细节对客户隐藏,在指令内对具体的ADT发出动作(调用ADT的细节操作)

  • 将“调用对象的方法”提升为完全对象状态
  • 面向对象的回调

问题

  • 需要向对象发出请求,而不知道请求的操作或请求的接收者。

客户端希望执行指令,但不想知道指令的细节,也不想知道指令的具体作用对象

命令将调用操作的对象与知道如何执行操作的对象分离。

  • 为了实现这种分离,设计人员创建了一个抽象基类,它将接收者(对象)与动作(指向成员函数的指针)进行映射。 基类包含一个execute()方法,它只是简单地调用接收方的动作。

每当客户端需要对象的“服务”时,Command对象的所有客户端都通过简单地调用对象的虚拟execute()方法将每个对象视为“黑盒子”。
将所有对客户提供的指令与内部执行的ADT操作彻底分开,指令对外看来是“黑盒” - 进一步“变本加厉”的封装!

一个Command类包含以下的一些子集:

  • 一个对象,一个应用于该对象的方法,以及该方法应用时传递的参数。
  • 命令的“执行”方法会导致碎片聚集在一起。

外观与命令

外观:结构模式
命令:行为模式

均强调对某个复杂系统内部提供的功能的“封装”,对外提供简单的调用接口,简化客户端的使用,“隐藏”细节。
命令:强调将指令封装为了“对象”,提供了统一的对外接口(执行)。
外观:没有显式的“对象”,仍然通过类的方法加以调用。

(5) Chain of responsibility

责任链模式

问题

  • 可能有多个“处理程序”或“处理元素”或“节点”对象,以及必须处理的请求流。

针对一个请求,可能有多个处理模块

  • 需要高效地处理请求,而无需硬连线处理程序关系和优先级或请求到处理程序映射。

各种不确定情况存在,不能以“硬编码”的方式指明按何种次序调用处理模块
意图

  • 通过给予多个对象机会来处理请求,避免将请求的发送者耦合到它的接收者。
  • 链接接收对象并沿着链传递请求,直到对象处理它为止。 使用包含许多可能的处理程序的单个处理管道启动和离开请求。 具有递归遍历的面向对象的链表。

避免在请求方和各处理者之间的紧耦合:构造流水线,请求在其上传,直到被处理为止。

将处理元素封装在“管道”抽象中; 并让客户在管道入口处“启动并离开”他们的请求。
客户端只需在流水线的入口发出请求即可,请求自动在流水线上传递,直到被处理。

该模式将接收对象链接在一起,然后将任何请求消息从对象传递到对象,直到它到达能够处理消息的对象。

  • 处理程序对象的数量和类型事先是未知的,它们可以动态配置。

处理器对象的数据和类型事先都不确定,需要动态配置

  • 链接机制使用递归组合来允许无限数量的处理程序进行链接。

使用递归组合的方式,将多个处理程序连成“职责链”
责任链简化了对象互连。

  • 发件人和收件人不是保留对所有候选收件人的引用,而是每个发件人对链的头部保持一个单一的引用,并且每个收件人对链中的其直接后继者保持单一引用。

请求的发起者无需维护所有可能的处理程序对象,只需记录职责链的入口;每个处理程序只需要维护“下一个”处理程序即可,从而简化了各个处理程序之间的连接关系。

访客与责任链

不是像传统类设计中将操作与数据捆绑在一起形成ADT,这两个设计模式都是将“数据”与“作用于数据上的客户端定制操作”分离开来
原因:操作可以灵活增加,运行时使用的操作可以动态配置,多个操作的执行次序可以动态变化

区别1:visitor只定义了一个操作,chain of responsibility定义了一组操作及其之间的次序
区别2:访客中,客户创建访客之后传入ADT,ADT再将执行权委托到visitor;责任链中,控制权完全在各个处理程序之间流转,ADT(请求对象)完全感受不到。

可重用性和可维护性设计模式的高层考虑

使用设计模式的线索(1)

文本:“独立于制造商”,“独立于设备”,“必须支持一系列产品”
=>抽象工厂模式
文本:“必须与现有对象接口”
=>适配器模式
文本:“必须与多个系统接口,其中一些系统将在未来开发”,“必须展示早期的原型”
=>桥模式
文本:“必须与现有的一组对象接口”
=>外墙模式

使用设计模式的线索(2)

文本:“复杂结构”,“必须具有可变的深度和宽度”
=>复合模式
文本:“必须是位置透明的”
=>代理模式
文本:“必须可扩展”,“必须可扩展”
=>观察者模式
文本:“必须提供独立于该机制的政策”
=>策略模式

总结

组合,适配器,桥接,包装,代理(结构模式)

  • 重点:组成物体形成更大的结构
    •从旧功能中实现新功能,
    •提供灵活性和可扩展性

命令,观察者,策略,模板(行为模式)

  • 重点:算法和对象的责任分配
    •避免与特定解决方案紧密耦合

抽象工厂,生成器(创造性模式)

  • 重点:创建复杂的对象
    •隐藏复杂对象的创建和组合

总结

创造性模式

  • 工厂方法,抽象工厂,Builder

结构模式

  • 桥接,代理,复合

行为模式

  • 中介,观察员,访问者,责任链,命令

对可重用性和可维护性设计模式的高层考虑


啥也博士
13 声望2 粉丝