设计模式
创造型模式
- abstract factory:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。(抽象工厂,对象)
- builder:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。(生成器,对象)
- factory method:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。使得一个类的实例化延迟到其子类。(工厂方法,类)
- prototype:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。(原型,对象)
- singleton:保证一个类仅有一个实例,并提供一个访问它的全局访问点。(单例,对象)
结构型模式
- adapter:将一个类的接口转换成客户希望的另一个接口。使得原本由于接口不兼容而不能一起工作的类可以一起工作。(适配器,类)
- bridge:将抽象部分与它的实现部分分离,使它们可以独立的变化。(桥接,对象)
- composite:将对象组合成树形结构以表示“部分-整体”的层次结构。使得客户对单个对象和复合对象的使用具有一致性。(组成,对象)
- decorator:动态地给一个对象添加一些额外的职责。就扩展功能而言,比生成子类的方式更灵活。(装饰,对象)
- facade:为子系统中的一组接口提供一个一致的页面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。(外观,对象)
- flyweight:运用共享技术有效地支持大量细粒度的对象。(享元,对象)
- proxy:为其他对象提供一个代理以控制对这个对象的访问。(代理,对象)
行为模式
- chain of responsibility:为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。(职责链,对象)
- command:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。(命令,对象)
- interpreter:给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。(解释器,对象)
- iterator:提供一种方法顺序访问一个集合对象中各个元素,而又不暴露该对象的内部表示。(迭代器,类)
- mediator:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。(中介者,对象)
- memento:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象外保存这个状态。这样以后就可将该对象恢复到保存的状态。(备忘录,对象)
- observer:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。(观察者,对象)
- state:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎改变了他所属的类。(状态,对象)
- strategy:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。此模式使得算法的变化可独立于它的客户。(策略,对象)
- template method:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。(模板方法,类)
- visitor:表示一个作用于某对象结构中的各元素的操作。他是你可以在不改变个元素的类的前提下定义作用于这些元素的新操作。(访问者,对象)
设计模式展示了如何使用面向对象的继承和实现、方法的重载和重写等基本技术,是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述。设计模式包含四个基本要素:模式名称、问题、解决方案、效果。各个模式之间经常会捆绑使用(composite、iterator、visitor)或互相替代(prototype、abstract factory)或尽管意图不同,但产生的设计结果相似(composite、decorator)。它为我们使用的各种技术提供了标准的名称和定义。算法和数据结构会有其命名和分类,但我们很少为其他类型的模式命名。设计模式就为设计者们交流讨论,书写文档以及探索更多不同的设计提供了一套通用的设计词汇。同时提供了一些常见问题的解决方案。
开发可复用软件时不得不重新组织或重构软件系统。设计模式可以帮助减少重构工作。面向对象的软件生命周期可分为三个阶段:原型阶段、扩展阶段和巩固阶段。首先建立一个快速原型,在此基础上进行增量式修改直到满足基本需求,然后进入扩展阶段新的需求要求加入新的类和操作甚至整个类层次,软件的不断扩展使其变得膨胀臃肿难以进行进一步修改。该软件若要继续演化就必须进行重新组织,这个过程称为 重构 。框架常常在这个阶段出现。重构工作包括将类拆分为专用和通用构件,并使各个类的接口合理化。
好的设计者不仅知道哪些变化会促使重构,还要知道哪些类和对象结构能够避免重构,使得设计对需求的变化具有健壮性。对需求的彻底分析有助于突出易于发生变化的需求。设计模式可以尽量防止以后的重构。
创造型模式
创建型模式抽象了实例化过程。它们帮助一个系统独立于如何创建、组合和表示它的那些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象。
随着系统演化得越来越依赖于对象复合而不是类继承,创建型模式变得更为重要。当这种情况发生时,重心从对一组固定行为的硬编码转移为定义一个较小的基本行为集,这些行为可以被组合成任意数目的更复杂的行为。这样创建有特定行为的对象要求的不仅仅是实例化一个类。
在这些模式中有两个不断出现的主旋律。第一,它们都将关于该系统使用哪些具体的类的信息封装起来。第二,它们隐藏了这些类的实例是如何被创建和放在一起的。整个系统关于这些对象所知道的是由抽象类所定义的接口。因此,创建型模式在什么被创建,谁创建它, 它是怎样被创建的,以及何时创建这些方面给予你很大的灵活性。它们允许你用结构和功能差别很大的“产品”对象配置一个系统。配置可以是静态的(即在编译时指定),也可以是动态的(在运行时)。
有时创建型模式是相互竞争的。例如,在有些情况下Prototype或Abstract Factory用起来都很好。而在另外一些情况下它们是互补的: Builder 可以使用其他模式去实现某个构件的创建。Prototype 可以在它的实现中使用Singleton。
用一个系统创建的那些对象的类对系统进行参数化有两种常用方法。一种是生成创建对象的类的子类;这对应于使用Factory Method 模式。这种方法的主要缺点是,仅为了改变产品类,就可能需要创建一个新的子类。这样的改变可能是级联的。例如,如果产品的创建者本身是由一个工厂方法创建的,那么你也必须重定义它的创建者。
另一种对系统进行参数化的方法更多的依赖于对象复合:定义一个对象负责明确产品对象的类,并将它作为该系统的参数。这是Abstract Factory、 Builder和Prototype模式的关键特征。所有这三个模式都涉及到创建一个新的负责创建产品对象的“工厂 对象”。Abstract Factory由这个工厂对象产生多个类的对象。Builder由这个工厂对象使用一个相对复杂的协议,逐步创建一个复杂产品。Prototype由该工厂对象通过拷贝原型对象来创建产品对象。在这种情况下,因为原型负责返回产品对象,所以工厂对象和原型是同一个对象。
考虑在Prototype模式中描述的绘图编辑器框架。可以有多种方法通过产品类来参数化GraphicTool:
- 使用Factory Method模式,将为选择板中的每个Graphic的子类创建一个GraphicTool的子类。GraphicTool将有一个NewGraphic操作, 每个GraphicTool的子类都会重定义它。
- 使用Abstract Factory模式,将有一个GraphicsFactory类层次对应于每个Graphic的子类。在这种情况每个工厂仅创建一个产品: CircleFactory将创建Circle, LineFactory将创建Line,等等。GraphicTool将以创建合适种类Graphic的工厂作为参数。
- 使Prototype模式,每个Graphic的子类将实现Clone操作,并且GraphicTool将以它所创建的Graphic的原型作为参数。
究竟哪一种模式最好取决于诸多因素。在我们的绘图编辑器框架中,第一眼看来, Factory Method模式使用是最简单的。它易于定义一个新的GraphicTool的子类,并且仅当选择板被定义了的时候,GraphicTool的实例才被创建。它的主要缺点在于GraphicTool子类数目的激增,并且它们都没有做很多事情。
Abstract Factory并没有很大的改进,因为它需要一个同样庞大的GraphicsFactory类层次。只有当早已存在一个GraphicsFactory类层次时,Abstract Factory才比Factory Method更好一点——或是因为编译器自动提供或是因为系统的其他部分需要这个GraphicsFactory类层次。
总的来说,Prototype模式对绘图编辑器框架可能是最好的,因为它仅需要为每个Graphics类实现一个Clone操作。这就减少了类的数目,并且Clone可以用于其他目的而不仅仅是纯粹的实例化。
Factory Method使一个设计可以定制且只略微有一些复杂。其他设计模式需要新的类,而Factory Method只需要一个新的操作。人们通常将Factory Method作为一种标准的创建对象的方法。但是当被实例化的类根本不发生变化或当实例化出现在子类可以很容易重定义的操作中(比如在初始化操作中)时,这就并不必要了。
使用Abstract Factory、 Prototype 或Builder的设计甚至比使用Factory Method的那些设计更灵活,但它们也更加复杂。通常,设计以使用Factory Method开始,并且当设计者发现需要更大的灵活性时,设计便会向其他创建型模式演化。当你在设计标准之间进行权衡的时候,了解多个模式可以给你提供更多的选择余地。
Abstract Factory(抽象工厂)——对象创造型模式
意图
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
别名
Kit
动机
- 在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。
- 当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式。
- 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。
- 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。
适用性
- 一个系统要独立于它的产品的创建、组合和表示时
- 一个系统要由多个产品系列中的一个来配置时
- 当强调一系列相关的产品对象的设计以便进行联合使用时
- 当提供一个产品类库,而只想显示它们的接口而不是实现时
结构
参与者
- AbstractFactory
- 声明一个创建抽象产品对象的操作接口
- ConcreteFactory
- 实现创建具体产品对象的操作
- AbstractProduct
- 为一列产品对象声明一个接口
- ConcreteProduct
- 定义一个将被相应的具体工厂创建的产品对象
- 实现AbstractProduct接口
- Client
- 仅适用由AbstractFactory和AbstractProduct类声明的接口
协作
- 通常在运行时刻创建一个ConcreteFactroy类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象,客户应使用不同的具体工厂
- AbstractFactory将产品对象的创建延迟到它的ConcreteFactory子类
优点
- 它分离了具体的类,因为一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离。客户通过它们的抽象接口操纵实例。产品的类名也在具体工厂的实现中被分离;它们不出现在客户代码中。
- 使得易于交换产品系列,一个工厂类用于创建一系列相关产品。这使得只需要改变具体的工厂即可使用不同的产品配置。
- 有利于产品一致性,可以使用同一个工厂来创建同一系列中的产品。
缺点
- 难以支持新种类的产品,支持新种类的产品就需要扩展该工厂的接口。
实现
- 将工厂作为单件,一个应用中一般每个产品系列只需要一个工厂实例。因此工厂通常最好实现为单例模式。
- 创建产品,抽象工厂仅声明一个创建产品的接口,真正创建产品是由其实现类实现的。最通常的办法是为每一个产品定义一个工厂方法。
- 定义可扩展的工厂,抽象工厂通常为每一个产品定义一个操作。增加新的产品需要改变接口及其实现类。一个更灵活但不太安全的设计是给创建对象的操作增加一个参数,该参数指定要被创建的对象的类型。
相关模式
AbstractFactory类通常用工厂方法(Factory Method)实现,但也可以用Prototype实现。一个具体的工厂通常是一个单件(Singleton)
Builder(生成器)——对象创建型模式
意图
将一个复杂对象的创建与它的表示分离,使得同样的构建过程可以创建不同的表示。
动机
一步步组织一个庞大的对象
适用性
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
结构
参与者
- Builder,为创建一个对象的各个部件指定抽象接口。
- ConcreteBuilder,实现Builder的接口以构造和装配该产品的各个部件。定义并明确它所创建的表示。提供一个检索产品的接口。
- Director,构造一个使用Builder接口的对象。
- Product,表示被构造的复杂对象。 ConcreteBuilderf创建该产品的内部表示并定义它的装配过程。包含定义组成部件的类,包括将这些部件装配成最终产品的接口。
协作
- 客户创建Director对象,并用它所想要的Builder对象进行配置
- 一旦产品部件被生成,导向器就会通知生成器
- 生成器处理导向器的请求,并将部件添加到该产品中
- 客户从生成器中检索产品
优点
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
缺点
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大
实现
通常有一个抽象的Builder类为导向者可能要求创建的每一个构件定义一个操作。这些操作默认情况下什么都不做。一个ConcreteBuilder类对它有兴趣创建的构件重新定义这些操作。
- 装配和构造接口,生成器逐步的构造它们的产品。因此Builder类接口必须足够普遍,以便为各种类型的具体生成器构造产品
- 为什么产品没有抽象类?通常情况下由具体生成器生成产品,它们的表示相差是如此之大以至于给不同的产品以公共父类没有太大意思
- 在Builder中默认的方法为空,在C++中,生成方法故意不声明为纯虚成员函数,而是把它们定义为空方法,这使客户只重定义他们所感兴趣的操作
相关模式
Abstract Factory与Builder相似,因为它也可以创建复杂的对象。主要的区别是Builder模式着重于一步步构造一个复杂对象。而Abstract Factory着重于多个系列的产品对象。Builder在最后一步返回产品,而对于Abstract Factory来说,产品是立即返回的。
Composite通常是用Builder生成的
Factory Method(工厂方法)——对象创建型模式
意图
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
别名
虚构造器(Virtual Constructor)
动机
将实际对象的创建交给工厂子类去完成
适用性
- 当一个类不知道它所必须创建的对象的类的时候
- 当一个类希望由它的子类来指定它所创建的对象的时候
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
结构
参与者
- Product
- 定义工厂方法所创建的对象的接口
- ConcreteProduct
- 实现Product接口
- Creator
- 声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象
- 可以调用工厂方法以创建一个Product对象
- ConcreteCreator
- 重定义工厂方法以返回一个ConcreteProduct实例
协作
- Creator依赖于它的子类来定义工厂方法,所以它返回一个适当的ConcreteProduct实例
优点
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类
- 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”
缺点
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度
实现
当应用FactoryMethod模式时需要考虑下面一些问题:
- 主要有两种不同的情况,第一种情况是Creator类是一个抽象类并且不提供它所声明的工厂方法的实现,需要子类来实现,因为没有合理的缺省实现,它避免了不得不实例化不可预见的类的问题。;第二种情况是Creator是一个具体的类而且为工厂方法提供一个缺省的实现。也有可能有一个定义了缺省实现的抽象类,但不常见,保证了子类的设计者能够在必要的时候改变父类所实例化的对象的类。
- 参数化工厂方法,该模式的另一种情况是的工厂方法可以创建多种产品。工厂方法采用一个标识要被创建的对象种类的参数。工厂方法创建的所有对象将工厂Product接口。
相关模式
Abstract Factory经常用工厂方法来实现。
工厂方法通常在Template Methods中被调用。
Prototypes不需要创建Creator的子类。但是,它们通常要求一个针对Product类的Initialize操作。Creator使用Initialize来初始化对象。而Factory Method不需要这样的操作。
Simple Factory(简单工厂)——对象创建型模式
意图
在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
别名
静态工厂方法(Static Factory Method)模式
动机
考虑一个简单的软件应用场景,一个软件系统可以提供多个外观不同的按钮(如圆形按钮、矩形按钮、菱形按钮等), 这些按钮都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观,如果我们希望在使用这些按钮时,不需要知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的按钮对象,此时,就可以使用简单工厂模式。
结构
适用性
- 工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
- 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。
优点
- 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
- 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点
- 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。 使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
- 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
- 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
Prototype(原型)——对象创建型模式
意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
适用性
当一个系统应该独立于它的产品创建、构成和表示时,要使用Prototype模式;以及
- 当要实例化的类是在运行时刻指定时,例如通过动态装载
- 为了避免创建一个与产品类层次平行的工厂类层次时
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些
结构
参与者
- Prototype
- 声明一个克隆自身的接口
- ConcretePrototype
- 实现一个克隆自身的操作
- Client
- 让一个原型克隆自身从而创建一个新的对象
协作
客户请求一个原型克隆自身
效果
对客户隐藏了具体的产品类,此外使用户无需改变即可使用与特定应用相关的类。
- 运行时刻增加和删除产品
- 改变值以指定新对象
- 改变结构以指定新对象
- 减少子类的构造
- 用类动态配置应用
实现
- 使用一个原型管理器
- 实现克隆操作
- 初始化克隆对象
-
相关模式
Prototype和Abstract Factory模式在某种方面是相互竞争的。但是它们也可以一起使用。Abstract Factory可以存储一个被克隆的原型的集合,并且返回产品对象。
大量使用Composite和Decorator模式的设计通常可以从Prototype模式处获益。
Singleton(单件)——对象创建型模式
意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用性
- 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
- 当这个唯一实例应该是通过子类化可扩展时,并且客户应该无需更改代码就能使用一个扩展的实例时
结构
参与者
- Singleton
- 定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作
- 可以负责创建它自己的唯一实例
协作
客户只能通过Singleton的Instance操作访问一个Singleton的实例
效果
- 对唯一实例的受控访问,因为Singleton类封装它的唯一实例,所以它可以严格的控制客户怎样以及何时访问它
- 缩小namespace,Singleton模式是对全局变量的一种改进,它避免了那些存储唯一实例的全局变量污染namespace
- 允许对操作和表示的精华,Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的
- 允许可变数目的实例,允许Singleton类的多个实例
缺点
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。
实现
- 保证一个唯一的实例
相关模式
AbstractFactory、Builder、Prototype都可以使用Singleton模式实现
结构型模式
结构型模式涉及到如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。一个简单的例子是采用多重继承方法将两个以上的类组合成一个类,结果这个类包含了所有父类的性质。这一模式尤其有助于多个独立开发的类库协同工作。另外一个例子是类形式的Adapter模式。一般来说,适配器使得一个接口(adaptee的接口)与其他接口兼容,从而给出了多个不同接口的统一抽象。为此,类适配器对一个adaptee类进行私有继承。这样适配器就可以用adaptee的接口标识它的接口。
结构型对象模式不是对结构和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能的一些方法。因为可以在运行时刻改变对象组合关系,所以对象组合方式具有更大的灵活性,而这种机制用静态类组合是不可能实现的。
Composite模式是结构型对象模式的一个实例。它描述了如何构造一个类层次结构,这一结构由两种类型对象(基元对象和组合对象)所对应的类构成,其中的组合对象使得你可以组合基元对象以及其他的组合对象,从而形成任意复杂的结构。在Proxy模式中,proxy对象作为其他对象的一个方便的替代或占位符。它的使用可以有多种形式。例如它可以在局部空间中代表一个远程地址空间中的对象,也可以表示一个要求被加载的较大的对象,还可以用来保护对敏感对象的访问。Proxy模式还提供了对对象的一些特有性质的一定程度上的间接访问,从而它可以限制、增强或修改这些性质。
Flyweight模式为共享对象定义了一个结构。至少有两个原因要求对象共享:效率和一致性。Flyweight的对象共享机制主要强调对象的空间效率。使用很多对象的应用必须考虑每一个对象的开销。使用对象共享而不是进行对象复制,可以节省大量的空间资源。但是仅当这些对象没有定义与上下文相关的状态时,它们才可以被共享。Flyweight的对象没有各样的状态。任何执行任务时需要的其他一些信息仅当需要时才传递过去。由于不存在与上下文相关的状态,因此Flyweight对象可以被自由的共享。
如果说Flyweight模式说明了如何生成很多较小的对象,那么Facade模式则描述了如何用单个对象表示整个子系统。模式中的facade用来表示一组对象,facade的职责是将消息转发给它所表示的对象。Bridge模式将对象的抽象和其实现分离,从而可以独立地改变它们。
Decoragtor模式描述了如何动态地为对象添加职责。Decorator模式是一种结构型模式。这一模式采用递归方式组合对象,从而允许你添加任意多的对象职责。例如,一个包含用户界面组件的Decorator对象可以将边框或阴影这样的装饰添加到该组件中,或者它可以将窗口和缩放这样的功能添加到组件中。我们可以将一个Decorator对象嵌套在另外一个对象中就可以很简单的增加两个装饰,添加其他的装饰也是如此。因此,每个Decorator对象必须与其他组件的接口兼容并且保证将消息传递给它。Decorator模式在转发一条消息之前或之后都可以完成它的工作。
Adapter模式和Bridge模式具有一些共同的特性。它们都给另一对象提供了一定程度上的间接性,因而有利于系统的灵活性。它们都涉及到从自身以外的一个接口向这个对象转发请求。
这些模式的不同之处主要在于它们各自的用途。Adapter模式主要是为了解决两个已有接口之间不匹配的问题。它不考虑这些接口是怎样实现的,也不考虑它们各自可能会如何演化。这种方式不需要对两个独立设计的类中的任一个进行重新设计,就能够使它们协同工作。另一方面,Bridge模式则与对抽象接口与它的实现部分进行桥接。虽然这一模式允许你修改实现了它的类,它仍然为用户提供了一个稳定的接口。Bridge模式也会在系统演化时适应新的实现。
由于这些不同点,Adapter和Bridge模式通常被用于软件生命周期的不同阶段。当你发现两个不兼容的类必须同时工作时,就有必要使用Adapter模式,其目的一般是为了避免代码重复。此处耦合不可预见。相反,Bridge的使用者必须事先知道:一个抽象将有多个实现部分,并且抽象和实现两者是独立演化的。Adapter模式在类已经设计好后实施;而Bridge模式在设计类之前实施。这并不意味着Adapter模式不如Bridge模式,只是因为它们针对了不同的问题。
你可能认为facade是另外一组对象的适配器。单这种解释忽视了一个事实:Facade定义一个新的接口,而Adapter则复用一个原有的接口。适配器使两个已有的接口协同工作,而不是定义一个全新的接口。
Composite模式和Decoratro模式具有类似的结构图,这说明它们都基于递归组合来组织可变数目的对象。Decorator旨在使你能够不需要生成子类即可给对象添加职责。这就避免了静态实现所有功能组合,从而导致子类急剧增加。Composite则有不同的目的,它旨在构造类,使多个相关的对象能够以统一的方式处理,而多重对象可以被当作一个对象来处理。它重点不在于修饰,而在于表示。
尽管它们的目的截然不同,但却具有互补性。因此Composite和Decorator模式通常协同使用。在使用这两种模式进行设计时,我们无需定义新的类,仅需将一些对象插接在一起即可构建应用。这时系统中将有一个抽象类,它有一些composite子类和decorator子类,还有一些实现系统的基本构建模块。此时composite和decorator将拥有共同的接口。从Decorator模式的角度看,composite是一个ConcreteComponent。而从composite模式的角度看,decorator则是一个Leaf。
另一种与Decorator模式结构相似的模式是Proxy。这两种模式都描述了怎样为对象提供一定程度上的间接引用,proxy和decorator对象的实现部分都保留了指向另一个对象的指针,它们向这个对象发送请求。
像Decorator模式一样,Proxy模式构成一个对象并为用户提供一致的接口。但与Decorator模式不同的是,Proxy模式不能动态地添加或分离性质,它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者,例如实体在远程设备上,访问受到限制或者实体是持久存储的。
在Proxy模式中,实体定义了关键功能,而Proxy提供对它的访问。在Decorator模式中,组件仅提供了部分功能,而一个或多个Decorator负责完成其他功能。Decorator模式适用于编译时不能确定对象的全部功能的情况。这种开发性使递归组合成为Decorator模式中一个必不可少的部分。而在Proxy模式中则不是这样,因为Proxy模式强调一种代理与实体的关系,这种关系可以静态的表达。
模式间的这些差异非常重要,因为它们针对了面向对象设计过程中一些特定的经常发生问题的解决方法。但这并不意味着这些模式不能结合使用。
Adapter(适配器)——类对象结构型模式
意图
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
别名
包装器 Wrapper
动机
有时为复用而设计的工具类会因为接口不匹配而无法使用,这种情况需要适配器来适配
适用性
- 你想使用一个已经存在的类,而它的接口不符合你的需求
- 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作
- (仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口
结构
参与者
- Target
- 定义Client使用的与特定领域相关的接口
- Client
- 与符合Target接口的对象协同
- Adaptee +定义一个已经存在的接口,这个接口需要适配
- Adapter
- 对Adaptee的接口与Target接口进行适配
协作
Client在Adapter实例上调用一些操作。接着适配器调用Adaptee的操作实现这个请求。
优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性
- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”
类适配器模式还具有如下优点:
- 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强 对象适配器模式还具有如下优点:
- 一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口
缺点
类适配器模式的缺点如下:
- 对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。 对象适配器模式的缺点如下:
- 与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
实现
实现可插入的适配器:
- 使用抽象操作,在子类中丰富更多适配的接口
- 使用代理对象,将请求转发给代理对象
- 参数化适配器
相关模式
模式Bridge的结构与对象适配器类似,但是Bridge模式的出发点不同: Bridge目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而Adapter则意味着改变一个已有对象的接口。
Decorator模式增强了其他对象的功能而同时又不改变它的接口。因此decorator对应用程序的透明性比适配器要好。结果是decorator支持递归组合,而纯粹使用适配器是不可能实现这一点的。
模式Proxy在不改变它的接口的条件下,为另一个对象定义了一个代理。
Bridge(桥接)——对象结构型模式
意图
将抽象部分与它的实现部分分离,使它们都可以独立的变化。
别名
Handle/Body
动机
当一个抽象可能有多个实现时,通常用继承来协调它们。抽象类定义对该抽象的接口,而具体的子类则用不同方式加以实现。但是此方法有时不够灵活。继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分独立地进行修改、扩充和重用。
适用性
- 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换
- 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充
- 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译
- (C++)你想对客户完全隐藏抽象的实现部分。在C++中,类的表示在类接口中是可见的
- 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点
结构
参与者
- Abstraction(Window)
- 定义抽象类的接口
- 维护一个指向Implementor类型对象的指针
- RefinedAbstraction(Icon Window)
- 扩充由Abstraction定义的接口
- Implementor(WindowImp)
- 定义实现类的接口,该接口不一定要与Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲, Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作
- ConcreteImplementor (XwindowImp, PMWindowImp)
- 实现Implementor接口并定义它的具体实现
优点
- 分离接口及其实现部分。一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现
- 提高可扩充性。可以独立地对Abstraction和Implementor层次结构进行扩充
- 实现细节对客户透明。可以对客户隐藏实现细节
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法
缺点
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性
实现
- 仅有一个Implementor。在仅有一个实现的时候,没有必要创建一个抽象的Implementor类。这是Bridge模式的退化情况;在Abstraction 与Implementor之间有一种一对一的关系。尽管如此,当你希望改变一个类的实现不会影响已有的客户程序时,模式的分离机制还是非常有用的,也就是说,不必重新编译它们,仅需重新连接即可。
- 创建正确的Implememtor对象。如果Abstraction知道所有的ConcreteImplementor类,则可以在它的构造器中进行实例化,通过传递给构造器的参数确定实例化哪一个类。另外一种方法是首先选择一个缺省实现,然后根据需要改变这个实现。也可以代理给另一个对象,由代理一次决定。
- 共享Implementor对象
- 采用多重继承机制
相关模式
Abstract Factory模式可以用来创建和配置一个特定的Bridge模式。 Adapter模式用来帮助无关的类协同工作,它通常在系统设计完成后才会被使用。然而,Bridge模式则是在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。
Composite(组合)——对象结构型模式
意图
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
动机
通过组合多个简单的组件来形成一个较大的组件,这些组件又可以组成更大的组件。
适用性
- 你想表示对象的部分-整体层次结构
- 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象
结构
参与者
- Component (Graphic)
- 为组合中的对象声明接口
- 在适当的情况下,实现所有类共有接口的缺省行为
- 声明一个接口用于访问和管理Component的子组件
- (可选)在递归结构中定义一个接口,用于访向一个父部件,并在合适的情况下实现它
- Leaf (Rectangle、 Line、Text等)
- 在组合中表示叶节点对象,叶节点没有子节点
- 在组合中定义图元对象的行为
- Composite (Picture)
- 定义有子部件的那些部件的行为
- 存储子部件。
- 在Component接口中实现与子部件有关的操作
- Client
- 通过Component接口操纵组合部件的对象
协作
用户使用Component类接口与组合结构中的对象进行交互。如果接收者是一个叶节点,则直接处理请求。如果接收者是Composite,它通常将请求发送给它的子部件,在转发请求之前与/或之后可能执行一些辅助操作。
效果
- 定义了包含基本对象和组合对象的类层次结构基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象。
- 简化客户代码客户可以一致地使用组合结构和单个对象。通常用户不知道(也不关心)处理的是一个叶节点还是一个组合组件。这就简化了客户代码,因为在定义组合的那些类中不需要写一些充斥着选择语句的函数。
- 使得更容易增加新类型的组件新定义的Composite或Leaf子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的Component类而改变。
- 使你的设计变得更加一般化容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。
实现
- 显式的父部件引用。保持从子部件到父部件的引用能简化组合结构的遍历和管理。父部件引用可以简化结构的上移和组件的删除,同时父部件引用也支持Chain of Responsibility模式。通常在Component类中定义父部件引用。Leaf和Composite类可以继承这个引用以及管理这个引用的那些操作。
- 共享组件。共享组件是很有用的,比如它可以减少对存贮的需求。但是当一个组件只有一个父部件时,很难共享组件。一个可行的解决办法是为子部件存贮多个父部件,但当一个请求在结构中向上传递时,这种方法会导致多义性。Flyweight模式讨论了如何修改设计以避免将父部件存贮在一起的方法。如果子部件可以将一些状态(或是所有的状态)存储在外部,从而不需要向父部件发送请求,那么这种方法是可行的。
- 最大化Component接口Composite模式的 目的之一是使得用户不知道他们正在使用的具体的Leaf和Composite类。为了达到这一目的,Composite类应为Leaf和Composite类尽可能多定义一些公共操作。Composite类通常为这些操作提供缺省的实现,而Leaf和Composite子类可以对它们进行重定义。
- 在Component中声明管理子部件的操作有更好的透明性;在Composite及其子类中声明管理子部件的操作有更好的安全性
- 当该结构中子类数目相对较少时,才需要在Component类中将子节点集合定义为一个实例变量,而这个Component类中也声明了一些操作对子节点进行访问和管理。
- 子部件排序。如果需要考虑子节点的顺序时,必须仔细地设计对子节点的访问和管理接口,以便管理子节点序列。Iterator模式可以在这方面给予一些指导。
- 使用高速缓冲存贮改善性能。如果你需要对组合进行频繁的遍历或查找,Composite类可以缓冲存储对它的子节点进行遍历或查找的相关信息。Composite可以缓冲存储实际结果或者仅仅是一些用于缩短遍历或查询长度的信息。
- 应该由谁删除Component。在没有垃圾回收机制的语言中,当一个Composite被销毁时,通常最好由Composite负责删除其子节点。但有一种情况除外,即Leaf对象不会改变,因此可以被共享。 9)存贮组件最好用哪一种数据结构。Composite可使用多种数据结构存贮它们的子节点,包括连接列表、树、数组和hash表。数据结构的选择取决于效率。事实上,使用通用数据结构根本没有必要。有时对每个子节点,Composite都有一个变量与之对应,这就要求Composite的每个子类都要实现自己的管理接口。
相关模式
通常部件父部件连接用于Responsibility of Chain模式。
Decorator模式经常与Composite模式一起使用。 当装饰和组合一起使用时,它们通常有一个公共的父类。因此装饰必须支持具有Add、Remove和GetChild操作的Component接口。
Flyweight让你共享组件,但不再能引用他们的父部件。
Itertor可用来遍历Composite。
Visitor将本来应该分布在Composite和Leaf类中的操作和行为局部化。
Decorator (装饰)——对象结构型模式
意图
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
别名
Wrapper
动机
有时我们希望给某个对象而不是整个类添加功能,如果使用继承机制会被所有实例继承,不够灵活。一种灵活的方式是将对象嵌入到另一个装饰对象中,由装饰对象来添加功能。
适用性
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
- 处理那些可以撤销的职责
- 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
结构
参与者
- Component
- 定义一个对象接口,可以给这些对象动态地添加职责
- ConcreteComponent
- 定义一个对象,可以给这个对象添加一些职责
- Decorator
- 维持一个指向Component对象的指针,并定义一个与Component接口一致的接口
- ConcreteDecorator
- 向组件添加职责
协作
Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作
优点
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”
缺点
- 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐
实现
- 接口的一致性,装饰对象的接口必须与它所装饰的Component的接口是一致的,因此,所有的ConcreteDecorator类必须有一个公共的父类(至少在C++中如此)。
- 省略抽象的Decorator类,当你仅需要添加一个职责时,没有必要定义抽象Decorator类。你常常需要处理现存的类层次结构而不是设计一个新系统,这时你可以把Decorator向Component转发请求的职责合并到ConcreteDecorator中。
- 保持Component类的简单性,为了保证接口的一致性,组件和装饰必须有一个公共的Component父类。因此保持这个类的简单性是很重要的;即,它应集中于定义接口而不是存储数据。对数据表示的定义应延迟到子类中,否则Component类会变得过于复杂和庞大,因而难以大量使用。赋予Component太多的功能也使得,具体的子类有一些它们并不需要的功能的可能性大大增加。
- 改变对象外壳与改变对象内核,我们可以将Decorator看作一个对象的外壳,它可以改变这个对象的行为。另外一种方法是改变对象的内核。例如,Strategy模式就是一个用于改变内核的很好的模式。 当Component类原本就很庞大时,使用Decorator模式代价太高,Strategy模式相对更好一些。在Strategy模式中,组件将它的一些行为转发给一个独立的策略对象,我们可以替换strategy对象,从而改变或扩充组件的功能。
相关模式
Adapte模式:Decorator模 式不同于Adapter模式,因为装饰仅改变对象的职责而不改变它的接口;而适配器将给对象一个全新的接口。
Composite模式:可以将装饰视为一个退化的、仅有一个组件的组合。然而,装饰仅给对象添加一些额外的职责——它的目的不在于对象聚集。
Strategy模式:用一个装饰你可以改变对象的外表;而Strategy模式使得你可以改变对象的内核。这是改变对象的两种途径。
Facade(外观)——对象结构型模式
意图
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
动机
将一个系统划分为若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小。方式之一就是引入一个外观对象,它为子系统中的一些设施提供了一个单一而简单的界面。
适用性
- 当要为一个复杂子系统提供一个简单接口时可以使用外观模式。该接口可以满足大多数用户的需求,而且用户也可以越过外观类直接访问子系统
- 客户程序与抽象类的实现部分之间存在着很大的依赖性。引人facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度
结构
参与者
- Facade (Compiler)
- 知道哪些子系统类负责处理请求。
- 将客户的请求代理给适当的子系统对象。
- Subsystem classes (Scanner、Parser 、ProgramNode等)
- 实现子系统的功能。
- 处理由Facade对象指派的任务。
- 没有facade的任何相关信息;即没有指向facade的指针。
协作
- 客户程序通过发送请求给Facade的方式与子系统通讯,Facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但Facade模式本身也必须将它的接口转换成子系统的接口。
- 使用Facade的客户程序不需要直接访问子系统对象。
优点
- 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少
- 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可
- Facade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层
- Facade模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要。可以有效降低降低编译依赖性
- 如果应用需要,它并不限制它们使用子系统类。因此你可以在系统易用性和通用性之间加以选择。
缺点
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”
实现
- 降低客户子系统之间的耦合度,用抽象类实现Facade而它的具体子类对应于不同的子系统实现,这可以进一步降低客户与子系统的耦合度。除生成子类的方法以外,另一种方法是用不同的子系统对象配置Facade对象。为定制facade,仅需对它的子系统对象(一个或多个)进行替换即可。
- 公共子系统类与私有子系统类,一个子系统与一个类的相似之处是,它们都有接口并且它们都封装了一些东西——类封装了状态和操作,而子系统封装了一些类。考虑一个类的公共和私有接口是有益的,我们也可以考虑子系统的公共和私有接口。
相关模式
Abstract Factory模式可以与Facade模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。Abstract Factory也可以代替Facade模式隐藏那些与平台相关的类。
Mediator模式与Facade模式的相似之处是,它抽象了一些已有的类的功能。然而,Mediator的目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能。Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,Facade模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道facade的存在。
通常来讲,仅需要一个Facade对象,因此Facade对象通常属于Singleton模式。
Flyweight(享元)——对象结构型模式
意图
运用共享技术有效地支持大量细粒度的对象。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
动机
面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。
适用性
- 一个应用程序使用了大量的对象
- 完全由于使用大量的对象,造成很大的存储开销
- 对象的大多数状态都可变为外部状态
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象
- 应用程序不依赖于对象标识。由于Flyweight对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值
结构
下面的对象图说明了如何共享flyweight。
参与者
- Flyweight (Glyph)
- 描述一个接口,通过这个接口flyweight可以接受并作用于外部状态
- ConcreteFlyweight(Character)
- 实现Flyweight接口,并为内部状态(如果有的话)增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的;即它必须独立于ConcreteFlyweight对象的场景
- UnsharedConcreteFlyweight (Row,Column)
- 并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。在Flyweight对象结构的某些层次UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点(Row和Column就是这样)
- FlyweightFactory
- 创建并管理flyweight对象
- 确保合理地共享flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)
- Client
- 维持一个对flyweight的引用
- 计算或存储一个(多个) flyweight的外部状态
协作
- flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部对象则由Client对象存储或计算。当用户调用flyweight对象的操作时,将该状态传递给它。
- 用户不应直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这可以保证对它们适当地进行共享。
优点
- 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份
- 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
缺点
- 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化
- 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长
实现
- 要注意使用享元模式并抽象出外部状态后的对象大小和数量,一定是要比不使用享元模式更少才对,不然没有意义。
- 管理共享对象,因为对象是共享的,用户不能直接对它进行实例化。可以使用对象工厂来提供获取共享对象的方式。对于大量的共享对象需要使用某种形式的引用计数和垃圾回收。而当共享对象数量很少时,完全可以永久保存。
相关模式
Flyweight模式通常和Composite模式结合起来,用共享叶结点的有向无环图实现一个逻辑上的层次结构。
通常,最好用Flyweight实现State和Strategy对象。
Proxy(代理)一对象结构型模式
意图
给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
别名
Surrogate
动机
在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。
通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对 象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
适用性
- 远程代理(RemoteProxy),为一个对象在不同的地址空间提供局部代表。
- 虚代理(Virtual Proxy),根据需要创建开销很大的对象。
- 保护代理(Protection Proxy),控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
- 智能指引(Smart Reference),取代了简单的指针,它在访问对象时执行一些附加操作。它的典型用途包括:
- 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它。
- 当第一次引用一个持久对象时,将它装人内存。
- 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
结构
这是运行时刻一种可能的代理结构的对象图
参与者
- Proxy
- 保存一个引用使得代理可以访问实体。若RealSubject和Subject的接口相同,Proxy会引用Subject。
- 提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。
- 控制对实体的存取,并可能负责创建和删除它。
- 其他功能依赖于代理的类型:
- RemoteProxy负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求。
- Virtual Proxy可以缓存实体的附加信息,以便延迟对它的访问。
- Protection Proxy检查调用者是否具有实现一个请求所必需的访问权限。
- Subject
- 定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。
- RealSubject
- 定义Proxy所代表的实体。
协作
代理根据其种类,在适当的时候向RealSubject转发请求。
优点
- 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
- 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求
- 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度
- 保护代理可以控制对真实对象的使用权限
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂
相关模式
Adapter:适配器Adapter为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作,因此,它的接口实际上可能只是实体接口的一个子集。
Decorator:尽管decorator的实现部分与代理相似,但decorator的目的不一样。Decorator为对象添加一个或多个功能,而代理则控制对对象的访问。
代理的实现与decorator的实现类似,但是在相似的程度上有所差别。Protection Proxy的实现可能与decorator的实现差不多。另一方面,Remote Proxy不包含对实体的直接引用,而只是一个间接引用,如“主机ID,主机上的局部地址。”Virtual Proxy开始的时候使用一个间接引用,例如一个文件名,但最终将获取并使用一个直接引用。
行为模式
行为模式涉及到算法和对象职责的分配。行为模式不仅描述对象或类的模式,还描述它们之间的通信模式。这些模式刻划了在运行时难以跟踪的复杂的控制流。它们将你的注意力从控制流转移到对象间的联系方式上来。
行为模式使用继承机制在类间分派行为。其中Template Method较为简单和常用。模板方法是一个算法的抽象定义,它逐步的定义该算法,每一步调用一个抽象操作或一个原语操作,自类定义抽象操作以具体实现该算法。另一种行为模式是Interpreter。它将一个文法表示为一个类层次,并实现一个解释器作为这些类实例上的一个操作。
行为对象模式使用对象复合而不是继承。一些行为对象模式描述了一组对象的对象怎样互相协作以完成其中任一个对象都无法单独完成的任务。这里一个重要的问题是对等的对象如何互相了解对方。对等对象可以保持显式的对对方的引用,但那会增加他们的耦合度。在极端情况下,每一个对象都要了解所有其他的对象。Mediator在对等对象间引入一个mediator对象以避免这种情况的出现。mediator提供了松耦合所需的间接性。
Chain of Responsibility提供更松的耦合。它让你通过一条候选对象链隐式的向一个对象发送请求。根据运行时刻情况任一候选者都可以响应相应的请求。候选者的数目是任意的,你可以在运行时刻决定哪些候选者参与到链中。
Observer模式定义并保持对象间的依赖关系。其他的行为对象模式常将行为封装在一个对象中并将请求指派给它。Strategy模式将算法封装在对象中,这样可以方便地指定和改变一个对象所使用的的算法。Command模式将请求封装在对象中,这样它就可作为参数来传递,也可以被存储在历史列表里,或者以其他方式使用。State模式封装一个对象的状态,使得当这个对象的状态对象变化时,该对象可改变它的行为。Visitor封装分布于多个类之间的行为,而Iterator则抽象了访问和遍历一个集合中的对象的方式。
各个行为模式之间是相互补充和相互加强的关系。例如,一个职责链中的类可能包含至少一个Template Method的应用。该模板方法可使用原语操作确定该对象是否应处理该请求并选择应转发的对象。职责链可以使用Commond模式将请求表示为对象。Iterpreter可以使用State模式定义语法分析上下文。迭代器可以遍历一个集合,而访问者可以对它的每一个元素进行一个操作。
行为模式也能与其他模式很好的协同工作。例如,一个使用Composite模式的系统可以使用一个访问者对该复合的各成分进行一些操作。它可以使用职责链使得各成分可以通过它们的父类访问某些全局属性。它也可以使用Decorater对该复合的某些部分的这些属性进行改写。它可以使用Observer模式将一个对象结构与另一个对象结构联系起来,可以使用State模式使得一个构件在状态改变时可以改变自身的行为。复合本身可以使用Builder中的方法创建,并且它可以被系统中的其他部分当作一个Prototype。
设计良好的面向对象式系统通常有多个模式镶嵌在其中,单其设计者却未必使用这些术语进行思考。然而,在模式级别而不是在类或对象级别上的进行系统组装可以使我们更方便地获取同等的协同性。
Chain of Responsibility(职责链)——对象行为型模式
意图
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
动机
命令的发送者并不明确的知道最终执行请求的接收者,接收者按照一定的先后关系组成一条链,其中收到请求的接收者要么亲自处理命令,要么向链的下游转发请求。
适用性
- 有多个对象可以处理一个请求,哪个对象处理该请求运行时确定
- 想要在不明确指定接收者的情况下,向多个对象中的一个提交请求
- 可处理一个请求的对象集合应被动态指定
结构
参与者
- Handler
- 定义一个处理请求的接口
- 可选,在Handler中实现链
- ConcreteHandler
- 处理它负责的请求
- 可访问它的后继者
- 如果可以处理请求就处理,否则向后传递请求
- Client
- 向职责链提交请求
优点
- 降低耦合度,接收者和发送者互相不耦合
- 增加了给对象指派职责的灵活性,可以再运行时动态对职责链进行修改
缺点
- 不保证一定会被处理,可能链内没有可以处理请求的对象,或者链没有正确的配置
实现
- 实现后继者链
- 定义新的链
- 使用已有的链
- 链接后继者
- 一个可选方式是,Handler不仅定义该请求的接口,同时也维护后继链接,这样Handler就提供了请求向后继者转发的缺省实现
- 表示请求
- 硬编码
- 使用专门的处理函数来处理双方约定的请求码
- 封装请求对象
相关模式
职责链通常与Composite一起使用,这样一个构件的父构件可作为它的后继处理对象。
Command(命令)——对象行为型模式
意图
将一个请求封装为一个对象,从而可以对客户的不同请求提供参数化调用;对请求排队或记录请求日志,以及支持可撤销的操作。
别名
动作(Action)、事务(Transaction)
动机
命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。
适用性
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
- 系统需要在不同的时间指定请求、将请求排队和执行请求
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,在Command接口中添加一个Unexecute操作用来取消上一次的Execute操作
- 记录命令日志,在Command接口中添加加载操作和存储操作,这样可以故障恢复
- 将一组操作组合成一个命令,即原子和宏命令
结构
参与者
- Command
- 抽象命令类,声明一个接口用来执行操作的接口
- ConcreteCommand (PasteCommand, OpenCommand)
- 将命令接受者绑定到一个命令
- 调用接收者相应的操作,以实现Execute
- Client (Appliction)
- 创建一个具体命令对象并设置它的接收者
- Invoker (MenuItem)
- 发出一个命令的请求调用
- Receiver (Document, Application)
- 知道如何执行一个请求命令相关的操作
协作
- Client创建一个ConcreteCommand对象并指定它的Receiver对象
- 让Invoker对象存储该ConcreteCommand对象以便后续发出命令
- Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤消的,ConcreteCommand就在执行Excute操作之前存储当前状态以用于取消该命令
- ConcreteCommand对象对调用它的Receiver的一系列操作以执行该请求
优点
- 降低系统耦合度,解耦命令发出者和命令接收者
- 新的命令可以很容易地加入到系统中,因为这无需改变已有的类
- 可以比较容易地设计一个命令队列和宏命令(组合命令)。一般说来宏命令是Composite模式的一个实例
- 可以方便地实现对请求的Undo和Redo
缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类
实现
- 一个命令对象的能力大小,它可以仅是一个连接发起者和接收者的命令,也可以实现所有的动作而不需要命令接收者。当需要定义的命令与已有的类无关,且没有一个合适的接收者;或该命令隐式的知道它的接收者时,可以使用第二种极端的方式
- 是否需要支持undo和redo,如果需要支持可能需要额外的状态信息:
- 接收者对象,它真正执行处理该请求的各操作
- 接收者接收到操作的参数
- 如果处理请求的操作会改变接收者对象中的某些值,那么这些值也必须先存储起来。接收者还必须提供一些操作,以使该命令可将接收者恢复到它先前的状态
- 如果只支持一次取消,那次只存储最近一次被执行的命令。要支持多级取消和重做,要有一个历史命令列表
- 避免取消操作过程中的错误积累,由于命令重复执行、取消执行可能会累计错误,以至一个应用的状态最终偏离初始值。这就必要在Command中存储更多的信息。可以使用Memento模式来让该Command访问这些信息而不暴露其他对象的内部信息
- 对于无多余操作的简单命令,可以使用泛型实现
相关模式
Composite模式可被用来实现宏命令。 Memento模式可用来保持某个状态,命令用这一状态来取消它的效果。在被放入历史表列前必须被拷贝的命令起到一种原型的作用。
Interpreter(解释器)——类行为模式
意图
文法解析
动机
如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。例如正则表达式,SpEL。
结构
参与者
- AbstractExpression
- 声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点共享
- TerminalExpression
- 实现与文法中的终结符相关联的解释操作
- 一个句子中的每个终结符需要该类的一个实例
- NonterminalExpression
- 对文法中的每一条规则R ::= R1R2…Rn都需要一个这个类
- 为从R1到Rn的每个符号都维护一个AbstractExpression类型的实例变量
- 为文法中的非终结符实现解释操作。解释一般要递归地调用表示R1到Rn的那些对象的解释操作
- Context
- 包含解释器之外的一些全局信息
- Client
- 构建表示该文法定义的语言中一个特定的句子的抽象语法树。该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成
- 调用解释操作
协作
- Client构建一个句子,它是NonterminalExpression和TerminalExpression的实例的一个抽象语法树。然后初始化上下文并调用解释操作
- 每一非终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解释操作构成了递归的基础
- 每一节点的解释操作用上下文来存储和访问解释器的状态
优点
- 易于改变和扩展文法,因为该模式使用类来表示文法规则,你可使用继承来改变或扩展该文法
- 也易于实现文法,定义抽象语法树中各个节点的类的实现大体类似。这些类易于直接编写,通常它们也可用一个编译器或语法分析程序生成器自动生成
- 复杂的文法难以维护,解释器模式为文法中的每一条规则至少定义了一个类。因此包含许多规则的文法可能难以管理和维护。可应用其他的设计模式来缓解这一问题。但当文法非常复杂时,其他的技术如语法分析程序或编译器生成器更为合适
- 增加了新的解释表达式的方式,解释器模式使得实现新表达式“计算”变得容易。例如,你可以在表达式类上定义一个新的操作以支持优美打印或表达式的类型检查。如果你经常创建新的解释表达式的方式,那么可以考虑使用Visitor模式以避免修改这些代表文法的类
实现
- 创建抽象语法树,解释器模式并未解释如何创建一个抽象的语法树。换言之,它不涉及语法分析。抽象语法树可用一个表驱动的语法分析程序来生成,也可用手写的(通常为递归下降法)语法分析程序创建,或直接由Client提供
- 定义解释操作,并不一定要在表达式类中定义解释操作。如果经常要创建一种新的解释器,那么使用Visitor模式将解释放人一个独立的“访问者” 对象更好一些。例如,一个程序设计语言的会有许多在抽象语法树上的操作,比如类型检查、优化、代码生成。恰当的做法是使用一个访问者以避免在每一个类上都定义这些操作
- 与Flyweight模式共享终结符,在一些文法中,一个句子可能多次出现同一个终结符。此时最好共享那个符号的单个拷贝
相关模式
Composite模式:抽象语法树是一个复合模式的实例。 Flyweight模式:说明了如何在抽象语法树中共享终结符。 Iterator:解释器可用一个迭代器遍历该结构。 Visitor:可用来在一个类中维护抽象语法树中的各节点的行为。
Iterator(迭代器)——对象行为型模式
意图
提供一种不需要暴露对象内部成员变量的方法来顺序访问一个集合对象中各个元素。
别名
游标 Cursor。
动机
将对集合类的访问和遍历从集合类对象中分离出来放入一个迭代器对象中。迭代器类定义了一个访问该列表元素的接口。迭代器对象负责跟踪当前元素。不同的迭代器实现可以实现不同的遍历策略而无需体现在集合类接口中。
迭代器和集合类是耦合在一起的。集合对象负责创建相应的迭代器,并负责关联集合类和迭代器。创建迭代器是一个Factory Method模式。
适用性
- 访问一个集合对象的内容而无需暴露它的内部表示
- 支持对集合对象的多种遍历
- 为遍历不同的集合结构提供一个统一的接口,即支持多态迭代
结构
参与者
- Iterator
- 迭代器定义访问和遍历元素的接口
- ConcreteIterator
- 具体迭代器实现迭代器接口
- 对该集合遍历时跟踪当前位置
- Aggregate
- 集合定义创建相应迭代器对象的接口
- ConcreteAggregate
- 具体集合实现创建相应迭代器的接口,该操作返回ConcreteIterator的一个适当的实例
协作
ConcreteIterator跟踪集合中的当前对象,并能够计算出待遍历的后继对象
优点
- 它支持以不同的方式遍历一个集合,复杂的集合可用多种方式进行遍历
- 迭代器简化了集合的接口,有了迭代器的遍历接口,集合本身就不再需要类似的遍历接口
- 在同一个集合上可以有多个遍历,每个迭代器保持它自己的遍历状态,因此可以同时进行多个遍历
实现
-
谁控制该迭代,当由客户来控制迭代时,该迭代器称为一个外部迭代器, 而当由迭代器控制迭代时,该迭代器称为一个内部迭代器。使用外部迭代器的客户必须主动推进遍历的步伐,显式地向迭代器请求下一个元素。相反地,若使用内部迭代器,客户只需向其提交一个待执行的操作,而迭代器将对集合中的每一个元素实施该操作
-
谁定义遍历算法,迭代器不是唯一可定义遍历算法的地方。集合本身也可以定义遍历算法,并在遍历过程中用迭代器来存储当前迭代的状态。我们称这种迭代器为一个游标,因为它仅用来指示当前位置。客户会以这个游标为一个参数调用该集合的Next操作,而Next操作将改变这个指示器的状态。如果迭代器负责遍历算法,那么将易于在相同的集合上使用不同的迭代算法,同时也易于在不同的集合上重用相同的算法。从另一方面说,遍历算法可能需要访问集合的私有变量。如果这样,将遍历算法放入迭代器中会破坏集合的封装性
-
迭代器健壮程度如何,如果在遍历集合的时候增加或删除该集合元素,可能会导致两次访问同一个元素或者遗漏掉某个元素。一个简单的解决办法是拷贝该集合,并对该拷贝实施遍历,但一般来说这样做代价太高。一个健壮的迭代器保证插人和删除操作不会干扰遍历,且不需拷贝该集合。有许多方法来实现健壮的迭代器。其中大多数需要向这个集合注册该迭代器。当插人或删除元素时,该集合要么调整迭代器的内部状态,要么在内部的维护额外的信息以保证正确的遍历
-
附加的迭代器操作,迭代器的最小接口由First、Next、IsDone和CurrentItem操作组成。其他一些操作可能也很有用。例如,对有序的集合可用一个Previous操作将迭代器定位到前一个元素。SkipTo操作用于已排序并做了索引的集合中,它将迭代器定位到符合指定条件的元素对象上
-
迭代器可有特权访问,迭代器可被看为创建它的集合的一个扩展。迭代器和集合紧密耦合。在C++中我们可让迭代器作为它的集合的一个友元来表示这种紧密的关系。这样你就不需要在集合类中定义一些仅为迭代器所使用的操作。但是,这样的特权访问可能使定义新的遍历变得很难,因为它将要求改变该集合的接口增加另一个友元。为避免这一问题,迭代器类可包含一些protected操作来访问集合类的重要的非公共可见的成员。迭代器子类可使用这些protected操作来得到对该集合的特权访问
-
用于复合对象的迭代器,在Composite模式中的那些递归集合结构上,外部迭代器可能难以实现,因为在该结构中不同对象处于嵌套集合的多个不同层次,因此一个外部迭代 器为跟踪当前的对象必须存储一条纵贯该Composite的路径。有时使用一个内部迭代器会更容易一些。它仅需递归地调用自己即可,这样就隐式地将路径存储在调用栈中,而无需显式地维护当前对象位置
-
空迭代器,一个空迭代器是一个退化的迭代器,它有助于处理边界条件,根据定义,一个NullIterator总是已经完成了遍历:即它的IsDone操作总是返回true。例如遍历树形结构时,向每一个节点请求其子节点的迭代器,当到叶子节点时,返回NullIterator的一个实例,可以用一种统一的方式实现遍历。
相关模式
Composite:迭代器常被应用到象复合这样的递归结构上。 Factory Method:多态迭代器靠Factory Method来例化适当的迭代器子类。 Memento:常与迭代器模式一起使用。迭代器可使用一个memento来捕获一个迭代的状态。迭代器在其内部存储memento。
Mediator(中介者)——对象行为型模式
意图
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
动机
虽然将一个系统分割成许多对象通常可以增强可复用性,但是对象间相互连接又会降低其可复用性。中介者负责控制和协调一组对象间的交互,以使组中的对象不再相互显示引用,这些对象仅知道中介者,减少了相互连接的数目。
适用性
- 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象
- 想定制一个分布在多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现。
- 交互的公共行为,如果需要改变行为则可以增加新的中介者类。
结构
参与者
- Mediator
- 中介者定义一个接口用于与各Colleague对象通信
- ConcreteMediator
- 具体中介者通过协调各Colleague实现协作行为
- 了解并维护它的各Colleague
- Colleague class
- 每个Colleague都知道它的中介者对象
- 每个Colleague在需与其他的Colleague通信的时候,与它的中介者通信。
协作
Colleague向一个中介者对象发送和接收请求。中介者在各Colleague间适当地转发请求以实现协作。
优点
- 减少了子类生成
- 它将各Collague解耦
- 它简化了对象交互
- 它对对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,使你将注意力从对象各自本身的行为转移到它们之间的交互上来。这有助于弄清楚一个系统中的对象是如何交互的
缺点
- 在具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护
实现
- 忽略抽象的Mediator类,当各Colleague仅与一个Mediator一起工作时, 没有必要定义一个抽象的Mediator类。Mediator类提供的抽象耦合已经使各Colleague可与不同的Mediator子类一起工作,反之亦然。
- Colleague与Mediator通信,当一个感兴趣的事件发生时, Colleague必须与其Mediator通信。一种实现方法是使用Observer模式,将Mediator实现为一个Observer,各Colleague作为Subject,一旦其状态改变就发送通知给Mediator。Mediator作出的响应是将状态改变的结果传播给其他的Colleague。另一个方法是在Mediator中定义一个特殊的通知接口,各Colleague在通信时直接调用该接口。
相关模式
Facade与中介者的不同之处在于它是对一个对象子系统进行抽象,从而提供了一个更为方便的接口。它的协议是单向的,即Facade对象对这个子 系统类提出请求,但反之则不 行。相反,Mediator提供了各Colleague对象不支持或不能支持的协作行为,而且协议是多向的。
Colleague可使用Observer模式与Mediator通信。
Memento(备忘录)——对象行为型模式
意图
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
别名
Token
动机
在不破坏对象的封闭原则基础上,记录一个对像的内部状态,以便随时将对象还原至记录过的状态。
适用性
- 保存一个对象的状态用于恢复
结构
参与者
- Memento(备忘录)
- 备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态
- 防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口,管理者(caretaker)只能看到备忘录的窄接口一它只能将备忘录传递给其他对象。相反,原发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态
- Originator(原发器)
- 原发器创建一个备忘录,用以记录当前时刻它的内部状态
- 使用备忘录恢复内部状态
- Caretaker(负责人)
- 负责保存好备忘录
- 不能对备忘录的内容进行操作或检查
协作
- 管理器向原发器请求一个备忘录,保留一段时间后,将其送回给原发器
- 备忘录是被动的。只有创建备忘录的原发器会对它的状态进行赋值和检索
优点
- 保持封装边界,把Originator内部信息对其他对象屏蔽
- 简化了原发器,让客户端管理它们请求的状态而不是由Originator自己管理,将会简化Originator,并且客户端工作结束时无需通知原发器
缺点
- 使用备忘录可能代价很高,在拷贝存储大量信息并频繁的创建和恢复的时候
- 衡量备忘录对客户端的窄接口和对原发器的宽接口
- 维护备忘录的潜在代价,因为管理器存储备忘录时不知道备忘录中有多少个状态。
实现
- 备忘录的两个接口:原发器所使用的宽接口和其他对象使用的窄接口需要语言支持
- 如果备忘录创建和恢复的顺序是可预测的,可以进存储原发器内部状态的增量改变
相关模式
- Command:命令可使用备忘录来为可撤销的操作维护状态
- Iterator:备忘录可用于迭代
Observer(观察者)——对象行为型模式
意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
别名
依赖(Dependents),发布——订阅(Publish-Subscribe)
动机
将一个系统分割成一系列相互协作的类会导致难以维护相关对象间的一致性。观察者模式解除因维护对象间一致性而带来的耦合。建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。
适用性
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用
- 当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。可以降低对象之间的耦合度
- 当一个对象必须通知其它对象,而你不希望这些对象是紧密耦合的
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制
结构
参与者
- Subject(目标)
- 目标知道它的观察者。可以有任意多个观察者观察同一个目标
- 提供注册和删除观察者对象的接口
- Observer (观察者)
- 为那些在目标发生改变时需获得通知的对象定义一个更新接口
- ConcreteSubject (具体目标)
- 将有关状态存人各ConcreteObserver对象
- 当它的状态发生改变时,向它的各个观察者发出通知
- ConcreteObserver (具体观察者)
- 维护一个指向ConcreteSubject对象的引用
- 存储有关状态,这些状态应与目标的状态保持一致
- 实现Observer的更新接口以使自身状态与目标的状态保持一致
协作
- 当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者
- 在得到一个具体目标的改变通知后, ConcreteObserver对象可向目标对象查询信息。ConcreteObserver使用这些信息以使它的状态与目标对象的状态一致
优点
- 目标和观察者间的抽象耦合
- 观察者模式符合“开闭原则”的要求
- 支持广播通信,目标发送的通知会广播给所有关联的观察者
- 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
缺点
- 依赖维护不当会导致意外更新。甚至循环调用导致系统崩溃
- 观察者的数量和量级如果不进行控制,由于不知道改变目标的最终代价,一个看上去无害的操作可能会引起一系列相关依赖进行更新
实现
- 创建目标到观察者之间的关系是目标保持观察者引用还是建立目标到观察者之间的映射
- 有些情况下允许一个观察者关联多个目标
- 谁触发更新,目标主动通知、观察者主动轮询、使用者主动调用更新
- 当删除一个目标时,要避免观察者还保持该目标的引用
- 确保目标发出通知时和观察者接受到通知时的状态是一致的
- 推拉模型选择,推模型需要目标保留观察者引用,难以复用;拉模型效率差,需要在没有目标对象帮助的情况下确定什么改变
- 可以让观察者显式地订阅感兴趣的通知
- 封装复杂的更新语义,当目标和观察者依赖关系复杂时需要管理器维护:
- 它将一个目标映射到它的观察者并提供一个接口来维护这个映射。这就不需要由目标来维护对其观察者的引用,反之亦然
- 它定义一个特定的更新策略
- 根据一个目标的请求,它更新所有依赖于这个目标的观察者
相关模式
Mediator:通过封装复杂的更新语义, ChangeManager充当目标和观察者之间的中介者。 Singleto: ChangeManager可使用Singleton模式来保证它是唯一的并且是可全局访问的。
State(状态)——对象行为型模式
意图
允许一个对象在其内部状态改变时改变行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States)。
别名
状态对象
动机
在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。
适用性
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为
- 代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态
结构
参与者
- Context
- 定义客户感兴趣的接口
- 维护一个ConcreteState子类的实例,这个实例定义当前状态
- State
- 定义一个接口以封装与Context的一个特定状态相关的行为
- ConcreteState subclasses
- 每一子类实现一个与Context的一个状态相关的行为
协作
- Context将与状态相关的请求委托给当前的ConcreteState对象处理
- Context可将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可访问Context
- Context是客户使用的主要接口。客户可用状态对象来配置一个Context,一旦一个Context配置完毕,它的客户不再需要直接与状态对象打交道
- Context或ConcreteState子类都可决定哪个状态是另外哪一个的后继者,以及是在何种条件下进行状态转换
优点
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
缺点
- 状态模式的使用必然会增加系统类和对象的个数
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
- 状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码
实现
- 可以在Context中实现状态转换,也可以让State自己实现状态流转,需要Context增加一个家口,让State对象显式地设定Context当前的状态
- 用这种方法分散的逻辑可以很容易地定义新的State子类来修改和扩展该逻辑,缺点是各State子类之间产生了依赖
- 用基于映射表的方式映射状态转换,但是对表的查找通常不如函数调用效率高;表只能映射状态不能加入伴随状态转换的一些操作
- State对象是预创建单例还是随时用随时销毁,可根据状态的数量和转换的频率来选择
相关模式
- Flyweight解释了何时以及怎样共享状态对象
- 状态对象通常是Singleton
Strategy(策略)——对象行为型模式
意图
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
别名
Policy
动机
完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。使用一些类封装不同的算法,避免把算法硬编码。
适用性
- 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法
- 需要使用一个算法的不同变体,例如时间优先或空间优先的相同算法
- 算法使用客户不应该知道的数据。可使用策略模式隐藏数据结构
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移人它们各自的Strategy类中以代替这些条件语句
结构
参与者
- Strategy
- 定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法。
- ConcreteStrategy
- 以Strategy接口实现某具体算法。
- Context
- 用一个ConcreteStrategy对象来配置。
- 维护一个对Strategy对象的引用。
- 可定义一个接口来让Stategy访问它的数据。
协作
- Strategy和Context相互作用以实现选定的算法。当算法被调用时,Context可以将该算法所需要的所有数据都传递给该Stategy。或者, Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context。
- Context将它的客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrategy对象给该Context;这样,客户仅与Context交互。通常有一系列的ConcreteStrategy类可供客户从中选择。
优点
- Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能
- 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
- 消除了条件语句
- 可以选择相同行为的不同实现
- 策略模式提供了管理相关的算法族的办法
缺点
- 策略模式将造成产生很多策略类
- 客户必须了解不同的Strategy类,并自行决定使用哪一个
- Context可能会创建和初始化一些Strategy用不到的参数
实现
- 定义Strategy和Context接口,Strategy和Context接口必须使得ConcreteStrategy能够有效的访问它所需要的Context中的任何数据,反之亦然。
- 将Strategy作为模板参数
- 使Strategy对象成为可选的,提供缺省行为
相关模式
Flyweight: Strategy对象经常是很好的轻量级对象
Template Method(模板方法)——类行为型模式
意图
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
动机
通过使用抽象操作定义一个算法中的一些步骤,模板方法确定了它们的先后顺序,并允许子类改变其中具体步骤的实现方式。
适用性
- 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现
- 各子类中公共的行为应该被提取出来集中到一个公共父类中以避免代码重复
- 控制子类扩展,模板方法只在特定点调用”hook”操作,这样就只允许在这些点进行扩展
结构
参与者
- AbstractClass
- 定义抽象原语操作,具体的子类将重新定义它们以实现一个算法的各步骤
- 实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作
- ConcreteClass
- 实现原语操作以完成算法中与特定子类相关的步骤
协作
ConcreteClass靠AbstractClass来实现算法中不变的步骤
效果
- 模板方法是一种代码复用的基本技术,在类库中尤为重要,它们提取了类库中的公共行为
- 模板方法中的钩子操作提供了缺省的行为,一般是一个空操作,子类只有在需要的时候才进行扩展
实现
- 尽量减少原语操作 定义模板方法的一个重要目的是尽量减少一个子类具体实现该算法时必须重定义的那些原语操作的数目。需要重定义的操作越多,客户程序就越冗长。
- 命名约定 可以给应被重定义的那些操作的名字加上一个前缀以识别它们。
相关模式
- Factory Method 常被模板方法凋用
- Strategy 模板方法使用继承来改变算法的一部分。Strategy使用委托来改变整个算法.
Visitor(访问者)——对象行为型模式
意图
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
动机
使用Visitor模式,必须定义两个类层次: 一个对应于接受操作的元素(Node层次)另一个对应于定义对元素的操作的访问者(NodeVisitor层次)。给访问者类层次增加一个新的子类即可创建一个新的操作。只要不需要增加新的Node子类,我们就可以简单的定义新的NodeVisitor子类以增加新的功能。
适用性
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作
- 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好
结构
参与者
- Visitor(访问者)
- 为该对象结构中ConcreteElement的每一个类声明一个Visit操作。该操作的名字和特征标识了发送Visit请求给该访问者的那个类。这使得访问者可以确定正被访问元素的具体的类。这样访问者就可以通过该元素的特定接口直接访问它
- ConcreteVisitor(具体访问者)
- 实现每个由Visitor声明的操作。每个操作实现本算法的一部分,而该算法片断乃是对应于结构中对象的类。ConcreteVisitor为该算法提供了上下文并存储它的局部状态。这一状态常常在遍历该结构的过程中累积结果
- Element(元素)
- 定义一个Accept操作,它以一个访问者为参数
- ConcreteElement(具体元素)
- 实现Accept操作,该操作以一个访问者为参数
- ObjectStructure(对象结构)
- 能枚举它的元素
- 可以提供一个高层的接口以允许该访问者访问它的元素
- 可以是一个复合或是一个集合,如一个列表或一个无序集合。
协作
- 一个使用Visitor模式的客户必须创建一个ConcreteVisitor对象,然后遍历该对象结构,并用该访问者访问每一个元素。
- 当一个元素被访问时,它调用对应于它的类的Visitor操作。如果必要,该元素将自身作为这个操作的一个参数以便该访问者访问它的状态。
优点
- 访问者模式使得易于增加新的操作,访问者使得增加依赖于复杂对象结构的构件的操作变得容易了。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反,如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一类。
- 访问者集中相关的操作而分离无关的操作,相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中。无关行为却被分别放在它们各自的访问者子类中。这就既简化了这些元素的类,也简化了在这些访问者中定义的算法。所有与它的算法相关的数据结构都可以被隐藏在访问者中。
缺点
- 增加新的ConcreteElement类很困难,Visitor模式使得难以增加新的Element的子类。每添加一个新的ConcreteElement都要在Vistor中添加一个新的抽象操作,并在每一个ConcretVisitor类中实现相应的操作。有时可以在Visitor中提供一个缺省的实现,这一实现可以被大多数的ConcreteVisitor继承,但这与其说是一个规律还不如说是一种例外。所以在应用访问者模式时考虑关键的问题是系统的哪个部分会经常变化,是作用于对象结构上的算法呢还是构成该结构的各个对象的类。如果老是有新的ConcretElement类加入进来的话,Vistor类层次将变得难以维护。在这种情况下,直接在构成该结构的类中定义这些操作可能更容易一些。如果Element类层次是稳定的,而你不断地增加操作获修改算法,访向者模式可以帮助你管理这些改动。
- 通过类层次进行访问,一个迭代器可以通过调用节点对象的特定操作来遍历整个对象结构,同时访问这些对象。但是迭代器不能对具有不同元素类型的对象结构进行操作。
- 累积状态,当访问者访问对象结构中的每一个元素时,它可能会累积状态。如果没有访问者,这一状态将作为额外的参数传递给进行遍历的操作,或者定义为全局变量。
- 破坏封装,访问者方法假定ConcreteElement接口的功能足够强,足以让访问者进行它们的工作。结果是,该模式常常迫使你提供访问元素内部状态的公共操作,这可能会破坏它的封装性。
实现
每一个对象结构将有一个相关的Visitor类。这个抽象的访问者类为定义对象结构的每一个ConcreteElement类声明一个VisitConcreteElement操作。每一个Visitor上的Visit操作声明它的参数为一个特定的ConcreteElement,以允许该Visitor直接访问ConcreteElement的接口。ConcreteVistor类重定义每一个Visit操作,从而为相应的ConcreteElement类实现与特定访问者相关的行为。
- 谁负责遍历对象结构,一个访问者必须访问这个对象结构的每一个元素。我们可以将遍历的责任放到对象结构中,访问者中,或一个独立的迭代器对象中。通常由对象结构负责迭代。
相关模式
- Composite,访问者可以用于对一-个由Composite模式定义的对象结构进行操作
- Interpreter,访问者可以用于解释
附录——词汇表
- 抽象类(abstract class) 一种主要用于定义接口的类。抽象类中的部分或全部操作被延迟到子类中实现。抽象类不能实例化。
- 抽象耦合(abstract coupling) 若类A维护了一个指向类B的引用,称A抽象耦合于B。因为A指向的是对象类型而不是对象实例。
- 抽象操作(abstract operation) 一种声明了结构而没有实现的操作。比如 纯虚成员函数 。
- 相识关系(acquaintance relationship) 如果一个类指向另一个类,则两个类之间有相识关系。
- 集合对象(aggregate object) 一种包含子对象的对象。这些子对象称为集合对象的部分,而集合对象对它们负责。
- 集合关系(aggregation relationship) 集合对象与其部分之间的关系。类为其对象定义这种关系。
- 黑箱复用(black-box reuse) 一种基于对象组合的复用方式。这些被组合的对象之间并不开放各自的内部细节。
- 类(class) 类定义对象的接口个实现。规定对象的内部表示,定义对象可实施的操作。
- 类图(class diagram) 类图描述类及其内部结构和操作,以及类间的静态关系。
- 类操作(class operation) 以类而不是单独的对象为目标的操作。比如 静态成员函数 。
- 具体类(concrete class) 不含抽象操作的类,可以实例化。
- 构造器(constructor) 系统自动调用用来初始化新对象实例的操作。
- 耦合(coupling) 软件构件之间相互依赖的程度。
- 委托(delegation) 一个对象把发送给它的请求转发/委托为另一个对象。而受委托对象代表原对象执行请求操作。
- 设计模式(design pattern) 针对面向对象系统中重复出现的设计问题,提出一个通用的设计方案,并予以系统化的命令和动机解释。它描述了问题、解决方案、在什么条件下使用该解决方案及其效果。它还给出了实现要点和实例。该解决方案是解决该问题的一组精心安排的通用的类和对象,再经定制和实现就可用来解决特定上下文的问题。
- 析构器(destructor) 系统自动调用用来清理即将被删除的对象的操作。
- 动态绑定(dynamic binding) 在运行时刻才将一个请求与一个对象及其一个操作关联起来。
- 封装(encapsulation) 其结果是将对象的表示和实现隐藏起来。在对象之外,看不到其内部表示,也不能直接对其进行访问。操作是访问和修改对象表示的唯一途径。
- 框架(framework) 一组相互协作的类,形成某类软件的一个可复用设计。框架将设计划分为一组抽象类,并定义它们各自的职责及相互之间的合作,以此来指到体系结构级的设计。开发者通过继承框架中的类和组合其实例来定制框架以生成特定的应用。
- 友类(friend class) A为B的友类,A对B中的操作和数据有与B本身一样的访问权限。
- 继承(inheritance) 两个实体间的一种关系,其中一实体是基于另一个实体而定义的。子类继承父类的接口和实现,也叫派生类。类继承包含了接口继承和实现继承。接口继承以一个或多个已有接口为基础定义新的接口。
- 实例变量(instance variable) 定义部分对象表示的数据。也叫 数据成员 。
- 交互图(interaction diagram) 展示对象间请求流程的一种示意图。
- 接口(inteface) 一个对象所有操作定义的集合。接口刻画了一个对象可响应的请求的集合。
- 混入类(mixin class) 一种被设计为通过继承与其他类结合的类。混入类通常是抽象类。
- 对象(object) 一个封装了数据及作用于这些数据的操作的运行实体。
- 对象组合(object composition) 组装和组合一组对象以获得更复杂的行为。
- 对象图(object diagram) 描述运行时刻特定对象结构的示意图。
- 对象引用(object reference) 用于标识另一个对象的一个值。
- 操作(operation) 对象的数据仅能由其自身的操作来存取。对象受到请求时执行操作。 成员函数
- 重定义(overriding) 在一个子类中重定义父类继承下来的操作。
- 参数化类型(parameterized type) 一种含有未确定成分类型的类型。在使用时将未确定类型处理成参数。模板
- 父类(parent class) 被其他类继承的类。基类 、 祖先类
- 多态(polymorphism) 在运行时刻接口匹配的对象能互相替换的能力。
- 私有继承(private inheritance) 一种仅出于实现目的的继承。
- 协议(protocol) 接口概念的扩展,包含指明可允许的序列。
- 接收者(receiver) 一个请求的目标对象。
- 请求(request) 一个对象当受到其他对象的请求时执行相应的操作。通常请求又被称为消息。
- 型构(signature) 一个操作的型构定义了它的名称、参数和返回值。
- 子类(subclass) 继承了另一个类的类。 派生类
- 子系统(subsystem) 一组相互协作的类形成的一个相对独立的部分,完成一定的功能。
- 子类型(subtype) 如果一个类型的接口包含另一个类型的接口,则前一类型称为后一类型的子类型。
- 超类型(supertype) 为其他类型继承的父类型。
- 工具箱(toolkit) 一组提供实用功能的类,但它们并不包含任何具体应用的设计。
- 类型(type) 一个特定接口的名称。
- 白箱复用(white-box reuse) 一种基于类继承的复用。子类复用父类的接口和实现,但它也可能存取其父类的其他私有部分。