zoukankan      html  css  js  c++  java
  • java设计模式概述

    1.1 什么是设计模式

    Christopher Alexander说过:“每个模式描写叙述了一个在我们周围不断反复发生的问题,以及该问题的解决方式的核心。这样,你就能一次重新地使用该方案而不必做反复劳动”

    一般而言,一个模式有四个基本要素:

    1. 模式名称(pattern name) 一个助记名,它用一两个词来描写叙述模式的问题、解决方式和效果。模式名能够帮助我们思考,便于我们与其它人交流设计思想及设计结果。

    找到恰当的模式名也是我们设计模式编目工作的难点之中的一个。

    2. 问题(problem) 描写叙述了应该在何时使用模式。它解释了设计问题和问题存在的前因后果,它可能描写叙述了特定的设计问题,如如何用对象表示算法等。

    3. 解决方式(solution) 描写叙述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。

    由于模式就像一个模板,可应用于多种不同场合,所以解决方式并不描写叙述一个特定而详细的设计或实现,而是提供设计问题的抽象描写叙述和如何用一个具有一般意义的元素组合

    4. 效果(consequences) 描写叙述了模式应用的效果及使用模式应权衡的问题。

    虽然我们描写叙述

    1.2 Smalltalk MVC中的设计模式 

    1.3 描写叙述设计模式

    我们如何描写叙述设计模式呢?

    图形符号尽管非常重要也非常实用,却还远远不够,它们仅仅是将设计过程的结果简单记录为类和对象之间的关系。为了达到设计复用,我们必须同一时候记录设计产生的决定过程、选择过程和权衡过程。详细的样例也是非常重要的,它们让你看到实际的设计。

    我们将用统一的格式描写叙述设计模式,每个模式依据下面的模板被分成若干部分。

    模板具有统一的信息描写叙述结构,有助于你更easy地学习、比較和使用设计模式。

    模式名和分类

    模式名简洁地描写叙述了模式的本质。一个好的名字很重要,由于它将成为你的设计词汇表中的一部分。

    模式的分类反映了我们将在 1 . 5 节 介 绍 的 方 案 。

    意图

    是回答下列问题的简单陈述:设计模式是做什么的?它的基本原理和意图是什么?它解决的是什么样的特定设计问题?

    别名

    模式的其它名称。

    动机

    用以说明一个设计问题以及怎样用模式中的类、对象来解决该问题的特定情景。该情景会帮助你理解随后对模式更抽象的描写叙述。

    适用性

    什么情况下能够使用该设计模式?该模式可用来改进哪些不良设计?你如何识别这些情况?

    结构

    採用基于对象建模技术( O M T [ R B P + 9 1 ] 的 表 示 法 对 模 式 中 的 类 进 行 图 形 描 述 。 我 们 也使用了交互图 [ J C J O 9 2B O O 9 4 ] 来说明对象之间的请求序列和协作关系。

    附录 具体描写叙述了这些表示法。

    參与者

    指设计模式中的类和 /或对象以及它们各自的职责。


    协作
    模式的參与者如何协作以实现它们的职责。
    效果模式如何支持它的目标?

    使用模式的效果和所需做的权衡取舍?

    系统结构的哪些方面可

    以独立改变?

    实现

    实现模式时须要知道的一些提示、技术要点及应避免的缺陷,以及是否存在某些特定于实现语言的问题。

    代码演示样例

    用来说明如何用 C + + 或 S m a l l t a l k 实 现 该 模 式 的 代 码 片 段 。

    1.4 设计模式的编目

    从第 章開始的模式文件夹中共包括 2 3 个 设 计 模 式 。 它 们 的 名 字 和 意 图 列 举 如 下 , 以 使 你 有个基本了解。每一个模式名后括号里标出模式所在的章节 (我们整本书都将遵从这个约定 )

    A b s t r a c t F a c t o r y ( 3 . 1 ) :提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们详细的类。

    page13image19376

    A d a p t er ( 4 . 1 ) :将一个类的接口转换成客户希望的另外一个接口。

     A d a p t e r 模式使得原本因为接口不兼容而不能一起工作的那些类能够一起工作。

    B r i d g e ( 4 . 2 ) :将抽象部分与它的实现部分分离,使它们都能够独立地变化。

    B u i l d e r( 3 . 2 ) : 将 一 个 复 杂 对 象 的 构 建 与 它 的 表 示 分 离 , 使 得 同 样 的 构 建 过 程 可 以 创 建 不同的表示。

    C h a i n o f R e s p o n s i b i l i t y ( 5 . 1 ): 为 解 除 请 求 的 发 送 者 和 接 收 者 之 间 耦 合 , 而 使 多 个 对 象 都有机会处理这个请求。

    将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。

    C o m m a n d( 5 . 2 ):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行參数化;对请求排队或记录请求日志,以及支持可取消的操作。

    C o m p o s i t e ( 4 . 3 ) : 将 对 象 组 合 成 树 形 结 构 以 表 示 “ 部 分 -整 体 ” 的 层 次 结 构 。 C o m p o s i t e 使得客户对单个对象和复合对象的使用具有一致性。

    D e c o r a t o r ( 4 . 4 ) :动态地给一个对象加入一些额外的职责。就扩展功能而言, D e c o r a t o r 模式比生成子类方式更为灵活。

    F a c a d e ( 4 . 5 ):为子系统中的一组接口提供一个一致的界面, F a c a d e 模式定义了一个高层接口,这个接口使得这一子系统更加easy使用。

    F a c t o r y M e t h o d ( 3 . 3 ):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。

    F l y w e i g h t ( 4 . 6 ):运用共享技术有效地支持大量细粒度的对象。

    I n t e r p r e t e r ( 5 . 3 ) :给定一个语言 定义它的文法的一种表示,并定义一个解释器 该解释器使用该表示来解释语言中的句子。

    I t e r a t o r( 5 . 4 ) :提供一种方法顺序訪问一个聚合对象中各个元素 而 又 不 需 暴 露 该 对 象 的内部表示。

    M e d i a t o r ( 5 . 5 ): 用 一 个 中 介 对 象 来 封 装 一 系 列 的 对 象 交 互 。 中 介 者 使 各 对 象 不 需 要 显 式地相互引用,从而使其耦合松散,并且能够独立地改变它们之间的交互。

    M e m e n t o( 5 . 6 ) : 在 不 破 坏 封 装 性 的 前 提 下 , 捕 获 一 个 对 象 的 内 部 状 态 , 并 在 该 对 象 之 外保存这个状态。

    这样以后就可将该对象恢复到保存的状态。

    O b s e r v e r ( 5 . 7 ) :定义对象间的一种一对多的依赖关系 以便当一个对象的状态发生改变时 ,全部依赖于它的对象都得到通知并自己主动刷新。

    P r o t o t y p e ( 3 . 4 ): 用 原 型 实 例 指 定 创 建 对 象 的 种 类 , 并 且 通 过 拷 贝 这 个 原 型 来 创 建 新 的 对象。

    P r o x y ( 4 . 7 ):为其它对象提供一个代理以控制对这个对象的訪问。
    S i n g l e t o n( 3 . 5 ) :保证一个类仅有一个实例,并提供一个訪问它的全局訪问点。


    S t a t e( 5 . 8 ) : 允 许 一 个 对 象 在 其 内 部 状 态 改 变 时 改 变 它 的 行 为 。 对 象 看 起 来 似 乎 修 改 了 它

    所属的类。
    S t r a t e g y (5 . 9 ) : 定 义 一 系 列 的 算 法 把 它 们 一 个 个 封 装 起 来 并 且 使 它 们 可 相 互 替 换 。 本 模

    式使得算法的变化可独立于使用它的客户。
    T e m p l a t e M e t h o d ( 5 . 1 0 ) :定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。

    Template Method使得子类能够不改变一个算法的结构就可以重定义该算法的某些特定步骤。

    page14image37168

    Vi s i t o r ( 5 . 11 ): 表 示 一 个 作 用 于 某 对 象 结 构 中 的 各 元 素 的 操 作 。

    它 使 你 可 以 在 不 改 变 各 元

    素的类的前提下定义作用于这些元素的新操作。

    1.5 组织编目

    设计模式在粒度和抽象层次上各不同样。因为存在众多的设计模式,我们希望用一种方式将它们组织起来。这一节将对设计模式进行分类以便于我们对各族相关的模式进行引用。分类有助于更快地学习文件夹中的模式,且对发现新的模式也有指导作用,如表1 - 1所看到的。

    我 们 根 据 两 条 准 则 表 1 - 1 ) 对 模 式 进 行 分 类 。

    第 一 是 目的 准 则 , 即 模 式 是 用 来 完 成 什 么 工作 的 。 模 式 依 据 其 目 的 可 分 为 创 建 型 C r e a t i o n a l )、 结 构 型 ( S t r u c t u r a l ) 、 或 行 为 型( B e h a v i o r a l ) 三种。

    创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象如何交互和如何分配职责进行描写叙述。

    第二是 范围 准则,指定模式主要是用于类还是用于对象。

    类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了。对象模式处理对象间的关系,这些关系在执行时刻是能够变化的,更具动态性。

    从某种意义上来说,差点儿全部模式都使用继承机制,所以“类模式”仅仅指那些集中于处理类间关系的模式,而大部分模式都属于对象模式的范畴。

    创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到还有一个对象中。

    结构型类模式使用继承机制来组合类,而结构型对象模式则描写叙述了对象的组装方式。行为型类模式使用继承描写叙述算法和控制流,而行为型对象模式则描写叙述一组对象如何协作完毕单个对象所无法完毕的任务。

    还 有 其 他 组 织 模 式 的 方 式 。

    有 些 模 式 经 常 会 被 绑 在 一 起 使 用 , 例 如 , C o m p o s i t e 常和I t e r a t o r 或 V i s i t o r 一起使用;有些模式是可替代的,比如, P r o t o t y p e 经常使用来替代 A b s t r a c tF a c t o r y ;有些模式虽然使用意图不同,但产生的设计结果是非常相似的,比如, C o m p o s i t eD e c o r a t o r 的结构图是相似的。

    另一种方式是依据模式的“相关模式”部分所描写叙述的它们如何互相引用来组织设计模式

    客户请求是使对象运行操作的唯一方法,操作又是对象改变内部数据的唯一方法。因为这些限制,对象的内部状态是被封装的,它不能被直接訪问,它的表示对于对象外部是不可见的。

    面向对象设计最困难的部分是将系统分解成对象集合。

    由于要考虑很多因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等等,它们都影响着系统的分解,而且这些因素通常还是互相冲突的。

    面向对象设计方法学支持很多设计方法。你能够写出一个问题描写叙述,挑出名词和动词,进而创建对应的类和操作;或者,你能够关注于系统的协作和职责关系;或者,你能够对现实世界建模,再将分析时发现的对象转化至设计中。至于哪一种方法最好,并无定论。

    设计的很多对象来源于现实世界的分析模型。

    可是,设计结果所得到的类通常在现实世界中并不存在,有些是像数组之类的低层类,而还有一些则层次较高。比如, C o m p o s i t e ( 4 . 3 ) 模式引入了统一对待现实世界中并不存在的对象的抽象方法。严格反映当前现实世界的模型并不能产生也能反映将来世界的系统。设计中的抽象对于产生灵活的设计是至关重要的。

    设计模式帮你确定并不明显的抽象和描写叙述这些抽象的对象。

    比如,描写叙述过程或算法的对象现实中并不存在,但它们却是设计的关键部分。 S t r a t e g y ( 5 . 9 ) 模 式 描 述 了 怎 样 实 现 可 互 换 的算法族。 S t a t e ( 5 . 8 ) 模 式 将 实 体 的 每 一 个 状 态 描 述 为 一 个 对 象 。

    这 些 对 象 在 分 析 阶 段 , 甚 至 在设计阶段的早期都并不存在,后来为使设计更灵活、复用性更好才将它们发掘出来。

    1.6.2 决定对象的粒度

    对象在大小和数目上变化极大。它们能表示下自硬件或上自整个应用的不论什么事物。那么我们如何决定一个对象应该是什么呢?

    设计模式非常好地讲述了这个问题。 F a c a d e ( 4 . 5 ) 模式描写叙述了如何用对象表示完整的子系统,F l y w e i g h t ( 4 . 6 )模 式 描 述 了 如 何 支 持 大 量 的 最 小 粒 度 的 对 象 。 其 他 一 些 设 计 模 式 描 述 了 将 一 个对象分解成很多小对象的特定方法。 A b s t r a c t F a c t o r y ( 3 . 1 ) 和 B u i l d e r ( 3 . 2 )产生那些专门负责生成 其 他 对 象 的 对 象 。 Vi s i t o r ( 5 . 1 0 ) 和 C o m m a n d ( 5 . 2 ) 生 成 的 对 象 专 门 负 责 实 现 对 其 他 对 象 或 对 象组的请求。

    1.6.3 指定对象接口

    对象声明的每个操作指定操作名、作为參数的对象和返回值,这就是所谓的操作的型构 ( s i g n a t u r e ) 。 对 象 操 作 所 定 义 的 所 有 操 作 型 构 的 集 合 被 称 为 该 对 象 的 接口 ( i n t e r f a c e ) 

    对象接口描写叙述了该对象所能接受的所有请求的集合,不论什么匹配对象接口中型构的请求都能够发送给该对象。

    类型 ( t y p e ) 是 用 来 标 识 特 定 接 口 的 一 个 名 字 。 如 果 一 个 对 象 接 受 “ W i n d o w ” 接 口 所 定 义的全部操作请求,那么我们就说该对象具有“ W i n d o w ” 类 型 。

    一 个 对 象 可 以 有 许 多 类 型 , 而且不同的对象能够共享同一个类型。对象接口的某部分能够用某个类型来刻画,而其它部分则可用其它类型刻画。两个类型同样的对象仅仅须要共享它们的部分接口。接口能够包括其它接口作为子集。当一个类型的接口包括还有一个类型的接口时,我们就说它是还有一个类型的子类型 ( s u b t y p e ) , 另 一 个 类 型 称 之 为 它 的 超类型 ( s u p e r t y p e ) 

    我 们 常 说 子 类 型 继承 了 它 的 超 类 型的接口。

    page17image28632

    在面向对象系统中,接口是主要的组成部分。

    对象仅仅有通过它们的接口才干与外部交流,假设不通过对象的接口就无法知道对象的不论什么事情,也无法请求对象做不论什么事情。对象接口与其功能实现是分离的,不同对象能够对请求做不同的实现,也就是说,两个有同样接口的对象能够有全然不同的实现。

    当给对象发送请求时,所引起的详细操作既与请求本身有关又与接受对象有关。支持同样请求的不同对象可能对请求激发的操作有不同的实现。发送给对象的请求和它的对应操作在执行时刻的连接就称之为动态绑定(dynamic binding)

    动态绑定是指发送的请求直到执行时刻才受你的详细的实现的约束。因而,在知道不论什么有正确接口的对象都将接受此请求时,你能够写一个一般的程序,它期待着那些具有该特定接口的对象。进一步讲,动态绑定同意你在执行时刻彼此替换有同样接口的对象。

    这样的可替换 性 就 称 为 多态 ( p o l y m o r p h i s m ) , 它 是 面 向 对 象 系 统 中 的 核 心 概 念 之 一 。 多 态 允 许 客 户 对 象 仅要求其它对象支持特定接口,除此之外对其如果几近于无。多态简化了客户的定义,使得对象间彼此独立,并能够在执行时刻动态改变它们相互的关系。

    设计模式通过确定接口的主要组成成分及经接口发送的数据类型,来帮助你定义接口。

    设计模式或许还会告诉你接口中不应包含哪些东西。 M e m e n t o ( 5 . 6 )模 式 是 一 个 非常 好 的 例 子 ,它描写叙述了如何封装和保存对象内部的状态,以便一段时间后对象能恢复到这一状态。它规定了 M e m e n t o 对象必须定义两个接口:一个同意客户保持和复制 m e m e n t o 的 限 制 接 口 , 和 一 个 仅仅有原对象才干使用的用来储存和提取 m e m e n t o 中 状 态 的 特 权 接 口 。

    设计模式也指定了接口之间的关系。

    特别地,它们常常要求一些类具有相似的接口;或它们对一些类的接口做了限制。比如, D e c o r a t o r ( 4 . 4 )和 P r o x y ( 4 . 7 )模式要求 D e c o r a t o r P r o x y对象的接口与被修饰的对象和受托付的对象一致。而 V i s i t o r ( 5 . 11 )模式中, V i s i t o r接口必须反映出 v i s i t o r 能訪问的对象的全部类。

    1.6.4 描写叙述对象的实现

    至此,我们非常少提及到实际上怎么去定义一个对象。对象的实现是由它的决定的,类指定了对象的内部数据和表示,也定义了对象所能完毕的操作,如右图所看到的。

    我们基于 O M T 的 表 示 法 , 将 类 描 述 成 一 个 矩 形 , 其 中 的 类 名 以 黑体表示的。

    操作在类名以下,以常规字体表示。类所定义的不论什么数据都在操作的以下。类名与操作之间以及操作与数据之间用横线切割。

    返回类型和实例变量类型是可选的,由于我们并未如果一定要用具有静态类型的实现语言。

    对象通过实例化 类来创建,此对象被称为该类的实例。当实例化类时,要给对象的内部数据(实例变量 组成)分配存储空间,并将操作与这些数据联系起来。

    对象的很多类似实例是由实例化同一个类来创建的。

    下图中的虚箭头线表示一个类实例化还有一个类的对象,箭头指向被实例化的对象的类。新 的 类 可 以 由 已 存 在 的 类 通 过 类继承 ( c l a s s i n h e r i t a n c e ) 来定义。当 子类 ( s u b c l a s s ) 继承 父类(parent class)时,子类包括了父类定义的全部数据和操作。子类的实例对象包括全部子类和父类定义的数据,且它们能完毕子类和父类定义的全部操作。我们以竖线和三角表示子类关系,例如以下图所看到的。

    page19image7136

    抽象类(abstract class)的主要目的是为它的子类定义公共接口。一个抽象类将把它的部分或所有操作的实现延迟到子类中,因此,一个抽象类不能被实例化。在抽象类中定义却没有实现的操作被称为抽象操作(abstract operation)

    非抽象类称为详细类(concrete class)

    子 类 能 够 改 进 和 重 新 定 义 它 们 父 类 的 操 作 。 更 具 体 地 说 , 类 能 够 重定义 ( o v e r r i d e ) 父类定义的操作,重定义使得子类能接管父类对请求的处理操作。类继承同意你仅仅需简单的扩展其它类就能够定义新类,从而能够非常easy地定义具有相近功能的对象族。

    抽象类的类名以斜体表示,以与详细类相差别。抽象操作也用斜体表示。

    图中能够包含实现操作的伪代码,假设这样,则代码将出如今带有摺角的框中,并用虚线将该摺角框与代码所实现的操作相连,图演示样例如以下。

    混入类(mixin class)是给其它类提供可选择的接口或功能的类。它与抽象类一样不能实例化。混入类要求多继承,图演示样例如以下。

    1. 类继承与接口继承的比較
    理解对象的类( c l a s s象的类型( t y pe )间的区别很重要。一个对象的类定义了对象是如何实现的,同一时候也定义了对象的内部状态和操作的实现。

    类型,不同类的对象能够有同样的类型。当然,对象的类和类型是有紧密关系的。由于类定义了对象所能运行的操作,也定义了

    对象的类型。

    当我们说一个对象是一个类的实例时,即指该对象支持类所定义的接口。
    C + +和 E i ff e l语言的类既指定对象的类型又指定对象的实现。

     S m a l l t a l k程序不声明变量的类型,所以编译器不检查赋给变量的对象类型是否是该变量的类型的子类型。

    发送消息时需

    要检查消息接收者是否实现了该消息,但不检查接收者是否是某个特定类的实例。理解类继承和接口继承 (或子类型化 )之间的区别也十分重要。

    类继承依据一个对象的实现定义了还有一个对象的实现。

    简而言之,它是代码和表示的共享机制。然而,接口继承 (或子类

    型化)描写叙述了一个对象什么时候能被用来替代还有一个对象。由于很多语言并不显式地区分这两个概念,所以easy被混淆。在 C + + 和 E i ff e l语言中,继

    承 既 指 接 口 的 继 承 又 指 实 现 的 继 承 。

     C + + 中 接 口 继 承 的 标 准 方 法 是 公 有 继 承 一 个 含 (纯 虚成员函数的类。 C + + 中 纯 接 口 继 承 接 近 于 公 有 继 承 纯 抽 象 类 , 纯 实 现 继 承 或 纯 类 继 承 接 近 于 私 有继承。 S m a l l t a l k 中 的 继 承 仅仅 指 实 现 继 承 。 仅仅 要 任 何 类 的 实 例 支 持 对 变 量 值 的 操 作 , 你 就 可 以将这些实例赋给变量。

    虽然大部分程序设计语言并不区分接口继承和实现继承的区别,但使用中人们还是分别对待它们的。 S m a l l t a l k 程序猿通常将子类当作子类型 虽然有一些熟知的例外情况 [ C o o 9 2 ] ) ,C + + 程序猿通过抽象类所定义的类型来操纵对象。

    非常多设计模式依赖于这样的区别。

    比如, Chain of Responsibility(5.1)模式中的对象必须有一个公共的类型,但普通情况下它们不具有公共的实现。在 C o m p o s i t e ( 4 . 3 ) 模 式 中 , 构 件 定 义 了一个公共的接口,但 C o m p o s i t e 通常定义一个公共的实现。 C o m m a n d ( 5 . 2 ) 、 O b s e r v e r ( 5 . 7 ) S t a t e ( 5 . 8 ) 和 S t r a t e g y ( 5 . 9 ) 通常纯粹作为接口的抽象类来实现。

    2. 对接口编程,而不是对实现编程

    类继承是一个通过复用父类功能而扩展应用功能的基本机制。它同意你依据旧对象高速定义新对象。它同意你从已存在的类中继承所须要的绝大部分功能,从而差点儿无需不论什么代价就能够获得新的实现。

    然而,实现的复用仅仅是成功的一半,继承所拥有的定义具有同样接口的对象族的能力也是非常重要的 (通常能够从抽象类来继承 )。为什么?

    由于多态依赖于这样的能力。

    当继承被恰当使用时,全部从抽象类导出的类将共享该抽象类的接口。这意味着子类只加入或重定义操作,而没有隐藏父类的操作。这时,全部的子类都能响应抽象类接口中的请求,从而子类的类型都是抽象类的子类型。

    仅仅依据抽象类中定义的接口来操纵对象有下面两个优点:
    1) 客户无须知道他们使用对象的特定类型,仅仅须对象有客户所期望的接口。
    2) 客户无须知道他们使用的对象是用什么类来实现的,他们仅仅须知道定义接口的抽象类。

    这将极大地降低子系统实现之间的相互依赖关系,也产生了可复用的面向对象设计的如

    下原则:

    针对接口编程,而不是针对实现编程。

    不将变量声明为某个特定的详细类的实例对象,而是让它遵从抽象类所定义的接口。这是本书设计模式的一个常见主题。

    page20image24072

    当你不得不在系统的某个地方实例化详细的类 (即指定一个特定的实现 )时,创建型模式

    ( A b s t r a c t F a c t o r y ( 3 . 1 ) B u i l d e r ( 3 . 2 )F a c t o r y M e t h o d ( 3 . 3 ) P r o t o t y p e ( 3 . 4 ) 和 S i n g l e t o n ( 3 . 5 ) ) 能够帮你。通过抽象对象的创建过程,这些模式提供不同方式以在实例化时建立接口和实现的透明连接。创建型模式确保你的系统是採用针对接口的方式书写的,而不是针对实现而书写的。

    1.6.5 运用复用机制

    理解对象、接口、类和继承之类的概念对大多数人来说并不难,问题的关键在于如何运用它们写出灵活的、可复用的软件。

    设计模式将告诉你如何去做。

    1. 继承和组合的比較

    面向对象系统中功能复用的两种最经常使用技术是类继承和对象组合(object composition)

    正如我们已解释过的,类继承同意你依据其它类的实现来定义一个类的实现。这样的通过生成子类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类可见。

    对象组合是类继承之外的还有一种复用选择。新的更复杂的功能能够通过组装或组合对象来获得。

    对象组合要求被 组合 的对象具有良好定义的接口。这样的复用风格被称为 黑箱复用(black-box reuse),由于对象的内部细节是不可见的。对象仅仅以“黑箱”的形式出现。

    继承和组合各有优缺点。类继承是在编译时刻静态定义的,且可直接使用,由于程序设计语言直接支持类继承。

    类继承能够较方便地改变被复用的实现。当一个子类重定义一些而不是所有操作时,它也能影响它所继承的操作,仅仅要在这些操作中调用了被重定义的操作。

    可是类继承也有一些不足之处。

    首先,由于继承在编译时刻就定义了,所以无法在执行时刻改变从父类继承的实现。更糟的是,父类通常至少定义了部分子类的详细表示。

    由于继承对子类揭示了其父类的实现细节,所以继承常被觉得“破坏了封装性” [ S n y 8 6 ] 

    子类中的实现与它的父类有如此紧密的依赖关系,以至于父类实现中的不论什么变化必定会导致子类发生变化。

    当你须要复用子类时,实现上的依赖性就会产生一些问题。

    假设继承下来的实现不适合解决新的问题,则父类必须重写或被其它更适合的类替换。这样的依赖关系限制了灵活性并终于限制了复用性。

    一个可用的解决方法就是仅仅继承抽象类,由于抽象类通常提供较少的实现。

    对象组合是通过获得对其它对象的引用而在执行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更细致地定义接口,而这些接口并最好还是碍你将一个对象和其它对象一起使用。这还会产生良好的结果:由于对象仅仅能通过接口訪问,所以我们并不破坏封装性;仅仅要类型一致,执行时刻还能够用一个对象来替代还有一个对象;更进一步,由于对象的实现是基于接口写的,所以实现上存在较少的依赖关系。

    对象组合对系统设计还有还有一个作用,即优先使用对象组合有助于你保持每一个类被封装,并被集中在单个任务上。

    这样类和类继承层次会保持较小规模,而且不太可能增长为不可控制的庞然大物。

    还有一方面,基于对象组合的设计会有很多其它的对象 (而有较少的类),且系统的行为将依赖于对象间的关系而不是被定义在某个类中。

    这导出了我们的面向对象设计的第二个原则:

    优先使用对象组合,而不是类继承。

    理想情况下,你不应为获得复用而去创建新的构件。你应该可以仅仅使用对象组合技术,

    通过组装已有的构件就能获得你须要的功能。

    可是事实非常少如此,由于可用构件的集合实际上并不足够丰富。使用继承的复用使得创建新的构件要比组装旧的构件来得easy。这样,继承和对象组合常一起使用。

    然而,我们的经验表明:设计者往往过度使用了继承这样的复用技术。但依赖于对象组合技术的设计却有更好的复用性 (或更简单)

    你将会看到设计模式中一再使用对象组合技术。

    2. 托付

    托付( d e l e g a t i o n 是一种组合方法,它使组合具有与继承相同的复用能力 [ L i e 8 6 , J Z 9 1 ] 。在托付方式下,有两个对象參与处理一个请求,接受请求的对象将操作托付给它的代理者
    (
    d e l e g a t e)。这类似于子类将请求交给它的父类处理。

    使用继承时,被继承的操作总能引用接受请求的对象, C + + 中通过 t h i s 成员变量, S m a l l t a l k 中则通过 s e l f 

    委 托 方 式 为 了 得 到 同 样 的效果,接受请求的对象将自己传给被托付者(代理人),使被托付的操作能够引用接受请求的对象。

    举例来说,我们能够在窗体类中保存一个矩形类的实例变量来代理矩形类的特定操作,这样窗体类能够复用矩形类的操作,而不必像继承时那样定义成矩形类的子类。

    也就是说,一个窗体拥有一个矩形,而不是一个窗体就是一个矩形。窗体如今必须显式的将请求转发给它的矩形实例,而不是像曾经它必须继承矩形的操作。

    以下的图显示了窗体类将它的 A r e a 操 作 委 托 给 一 个 矩 形 实 例 。

    箭 头 线 表 示 一 个 类 对 另 一 个 类 实 例 的 引 用 关 系 。 引 用 名 是 可 选 的 , 本 例 为 “ r e c t a n g l e ”。

    托付的主要长处在于它便于执行时刻组合对象操作以及改变这些操作的组合方式。假定矩形对象和圆对象有同样的类型,我们仅仅需简单的用圆对象替换矩形对象,则得到的窗体就是圆形的。

    托付与那些通过对象组合以取得软件灵活性的技术一样,具有例如以下不足之处:动态的、高度參数化的软件比静态软件更难于理解。还有执行低效问题,只是从长远来看人的低效才是更基本的。仅仅有当托付使设计比較简单而不是更复杂时,它才是好的选择。要给出一个能确切告诉你什么时候能够使用托付的规则是非常困难的。由于托付能够得到的效率是与上下文有关的,而且还依赖于你的经验。托付最适用于符合特定程式的情形,即标准模式的情形。

    有一些模式使用了托付,如 S t a t e ( 5 . 8 ) S t r a t e g y ( 5 . 9 ) Vi s i t o r ( 5 . 11 )。在 S t a t e模式中,一个对象将请求托付给一个描写叙述当前状态的 S t a t e 对象来处理。在 S t r a t e g y 模 式 中 , 一 个 对 象 将 一个特定的请求托付给一个描写叙述请求运行策略的对象,一个对象仅仅会有一个状态,但它对不同的请求能够有很多策略。这两个模式的目的都是通过改变受托对象来改变托付对象的行为。在 V i s i t o r 中 , 对 象 结 构 的 每 个 元 素 上 的 操 作 总 是 被 委 托 到 Vi s i t o r 对象。

    page22image21072

    其 他 模 式 则 没 有 这 么 多 地用到托付。

     M e d i a t o r ( 5 . 5 )引 进 了 一 个 中 介 其 他 对 象 间 通 信 的 对


    象。有时, M e d i a t o r 对 象 仅仅 是 简 单 地 将 请 求 转 发 给 其 他 对 象 ; 有 时 , 它 沿 着 指 向 自 己 的 引 用来传递请求,使用真正意义的托付。 Chain of Responsibility(5.1)通过将请求沿着对象链传递来处理请求,有时,这个请求本身带有一个接受请求对象的引用,这时该模式就使用了托付。

    B r i d g e ( 4 . 2 ) 将实现和抽象分离开,假设抽象和一个特定实现很匹配,那么这个实现能够代理抽象的操作。

    托付是对象组合的特例。它告诉你对象组合作为一个代码复用机制能够替代继承。

    3. 

  • 相关阅读:
    调试
    自定义缓冲函数
    缓冲
    如何控制动画
    开发中遇到过的坑
    动画控制属性
    自定义动画
    CATransition(过渡)
    动画基础(显式动画)
    呈现图层
  • 原文地址:https://www.cnblogs.com/yxysuanfa/p/7338158.html
Copyright © 2011-2022 走看看