diff --git a/_static/AbatractFactory_run.jpg b/_static/AbatractFactory_run.jpg new file mode 100644 index 0000000..9e965d4 Binary files /dev/null and b/_static/AbatractFactory_run.jpg differ diff --git a/_static/Builder_run.jpg b/_static/Builder_run.jpg new file mode 100644 index 0000000..14ec8a0 Binary files /dev/null and b/_static/Builder_run.jpg differ diff --git a/_static/Singleton_run.jpg b/_static/Singleton_run.jpg new file mode 100644 index 0000000..2f71ce2 Binary files /dev/null and b/_static/Singleton_run.jpg differ diff --git a/behavioral_patterns/command.rst b/behavioral_patterns/command.rst new file mode 100644 index 0000000..a90cf1b --- /dev/null +++ b/behavioral_patterns/command.rst @@ -0,0 +1,161 @@ +.. _command: + +命令模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +在软件设计中,我们经常需要向某些对象发送请求,但 +是并不知道请求的接收者是谁,也不知道被请求的操作 +是哪个,我们只需在程序运行时指定具体的请求接收者 +即可,此时,可以使用命令模式来进行设计,使得请求 +发送者与请求接收者消除彼此之间的耦合,让对象之间 +的调用关系更加灵活。 + +命令模式可以对发送者和接收者完全解耦,发送者与接 +收者之间没有直接引用关系,发送请求的对象只需要知 +道如何发送请求,而不必知道如何完成请求。这就是命 +令模式的模式动机。 + +模式定义 +-------------------- +命令模式(Command Pattern):将一个请求封装为一 +个对象,从而使我们可用不同的请求对客户进行参数化; +对请求排队或者记录请求日志,以及支持可撤销的操作。 +命令模式是一种对象行为型模式,其别名为动作 +(Action)模式或事务(Transaction)模式。 + + + +模式结构 +-------------------- +命令模式包含如下角色: +? Command: 抽象命令类 +? ConcreteCommand: 具体命令类 +? Invoker: 调用者 +? Receiver: 接收者 +? Client:客户类 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + + +模式分析 +-------------------- +命令模式的本质是对命令进行封装,将发出命令的责任和执行 +命令的责任分割开。 +?每一个命令都是一个操作:请求的一方发出请求,要求执行一 +个操作;接收的一方收到请求,并执行操作。 +?命令模式允许请求的一方和接收的一方独立开来,使得请求的 +一方不必知道接收请求的一方的接口,更不必知道请求是怎么 +被接收,以及操作是否被执行、何时被执行,以及是怎么被执 +行的。 +?命令模式使请求本身成为一个对象,这个对象和其他 +对象一样可以被存储和传递。 +?命令模式的关键在于引入了抽象命令接口,且发送者 +针对抽象命令接口编程,只有实现了抽象命令接口的 +具体命令才能与接收者相关联。 + + + +实例 +-------------------- +实例一:电视机遥控器 +? 电视机是请求的接收者,遥控器是请求的发送者,遥 +控器上有一些按钮,不同的按钮对应电视机的不同操 +作。抽象命令角色由一个命令接口来扮演,有三个具 +体的命令类实现了抽象命令接口,这三个具体命令类 +分别代表三种操作:打开电视机、关闭电视机和切换 +频道。显然,电视机遥控器就是一个典型的命令模式 +应用实例。 + + +优点 +-------------------- +命令模式的优点 +? 降低系统的耦合度。 +? 新的命令可以很容易地加入到系统中。 +? 可以比较容易地设计一个命令队列和宏命令(组合命 +令)。 +? 可以方便地实现对请求的Undo和Redo。 + + +缺点 +-------------------- +命令模式的缺点 +? 使用命令模式可能会导致某些系统有过多的具体命令 +类。因为针对每一个命令都需要设计一个具体命令类, +因此某些系统可能需要大量具体命令类,这将影响命 +令模式的使用。 + + +适用环境 +-------------------- +?在以下情况下可以使用命令模式: +? 系统需要将请求调用者和请求接收者解耦,使得调用者和 +接收者不直接交互。 +? 系统需要在不同的时间指定请求、将请求排队和执行请求。 +? 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。 +? 系统需要将一组操作组合在一起,即支持宏命令 + + +模式应用 +-------------------- +很多系统都提供了宏命令功能,如UNIX平台下的 +Shell编程,可以将多条命令封装在一个命令对象中, +只需要一条简单的命令即可执行一个命令序列,这也 +是命令模式的应用实例之一。 + + +模式扩展 +-------------------- +宏命令又称为组合命令,它是命令模式和组合模式联用 +的产物。 +?宏命令也是一个具体命令,不过它包含了对其他命令对 +象的引用,在调用宏命令的execute()方法时,将递归 +调用它所包含的每个成员命令的execute()方法,一个 +宏命令的成员对象可以是简单命令,还可以继续是宏命 +令。执行一个宏命令将执行多个具体命令,从而实现对 +命令的批处理 + +总结 +-------------------- +在命令模式中,将一个请求封装为一个对象,从而使我们可用不同的 +请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可 +撤销的操作。命令模式是一种对象行为型模式,其别名为动作模式或 +事务模式。 +? 命令模式包含四个角色:抽象命令类中声明了用于执行请求的execute() +等方法,通过这些方法可以调用请求接收者的相关操作;具体命令类 +是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具 +体的接收者对象,将接收者对象的动作绑定其中;调用者即请求的发 +送者,又称为请求者,它通过命令对象来执行请求;接收者执行与请 +求相关的操作,它具体实现对请求的业务处理。 +- 命令模式的本质是对命令进行封装,将发出命令的责任和执行命 +令的责任分割开。命令模式使请求本身成为一个对象,这个对象 +和其他对象一样可以被存储和传递。 +? 命令模式的主要优点在于降低系统的耦合度,增加新的命令很方 +便,而且可以比较容易地设计一个命令队列和宏命令,并方便地 +实现对请求的撤销和恢复;其主要缺点在于可能会导致某些系统 +有过多的具体命令类。 +? 命令模式适用情况包括:需要将请求调用者和请求接收者解耦, +使得调用者和接收者不直接交互;需要在不同的时间指定请求、 +将请求排队和执行请求;需要支持命令的撤销操作和恢复操作; +需要将一组操作组合在一起,即支持宏命令。 + + diff --git a/behavioral_patterns/mediator.rst b/behavioral_patterns/mediator.rst new file mode 100644 index 0000000..8dfd960 --- /dev/null +++ b/behavioral_patterns/mediator.rst @@ -0,0 +1,178 @@ +.. _mediator: + +中介者模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +- 在用户与用户直接聊天的设计方案中,用户对象之间存在很强的关 +联性,将导致系统出现如下问题: +? 系统结构复杂:对象之间存在大量的相互关联和调用,若有一个对象 +发生变化,则需要跟踪和该对象关联的其他所有对象,并进行适当处 +理。 +? 对象可重用性差:由于一个对象和其他对象具有很强的关联,若没有 +其他对象的支持,一个对象很难被另一个系统或模块重用,这些对象 +表现出来更像一个不可分割的整体,职责较为混乱。 +? 系统扩展性低:增加一个新的对象需要在原有相关对象上增加引用, +增加新的引用关系也需要调整原有对象,系统耦合度很高,对象操作 +很不灵活,扩展性差。 + +- 在面向对象的软件设计与开发过程中,根据“单一职责 +原则”,我们应该尽量将对象细化,使其只负责或呈现 +单一的职责。 +- 对于一个模块,可能由很多对象构成,而且这些对象之 +间可能存在相互的引用,为了减少对象两两之间复杂的 +引用关系,使之成为一个松耦合的系统,我们需要使用 +中介者模式,这就是中介者模式的模式动机。 + + +模式定义 +-------------------- +中介者模式(Mediator Pattern)定义:用一个中介对 +象来封装一系列的对象交互,中介者使各对象不需要显 +式地相互引用,从而使其耦合松散,而且可以独立地改 +变它们之间的交互。中介者模式又称为调停者模式,它 +是一种对象行为型模式。 + + +模式结构 +-------------------- +中介者模式包含如下角色: +? Mediator: 抽象中介者 +? ConcreteMediator: 具体中介者 +? Colleague: 抽象同事类 +? ConcreteColleague: 具体同事类 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + + +模式分析 +-------------------- +中介者模式可以使对象之间的关系数量急剧减少: + +中介者承担两方面的职责: +? 中转作用(结构性):通过中介者提供的中转作用,各个同 +事对象就不再需要显式引用其他同事,当需要和其他同事进 +行通信时,通过中介者即可。该中转作用属于中介者在结构 +上的支持。 +? 协调作用(行为性):中介者可以更进一步的对同事之间的 +关系进行封装,同事可以一致地和中介者进行交互,而不需 +要指明中介者需要具体怎么做,中介者根据封装在自身内部 +的协调逻辑,对同事的请求进行进一步处理,将同事成员之 +间的关系行为进行分离和封装。该协调作用属于中介者在行 +为上的支持。 + + +实例 +-------------------- +实例:虚拟聊天室 +? 某论坛系统欲增加一个虚拟聊天室,允许论坛会员通过该 +聊天室进行信息交流,普通会员(CommonMember)可以给 +其他会员发送文本信息,钻石会员(DiamondMember)既可 +以给其他会员发送文本信息,还可以发送图片信息。该聊 +天室可以对不雅字符进行过滤,如“日”等字符;还可以 +对发送的图片大小进行控制。用中介者模式设计该虚拟聊 +天室。 + + +优点 +-------------------- +中介者模式的优点 +? 简化了对象之间的交互。 +? 将各同事解耦。 +? 减少子类生成。 +? 可以简化各同事类的设计和实现。 + + +缺点 +-------------------- +中介者模式的缺点 +? 在具体中介者类中包含了同事之间的交互细节,可能 +会导致具体中介者类非常复杂,使得系统难以维护。 + + +适用环境 +-------------------- +在以下情况下可以使用中介者模式: +? 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结 +构混乱且难以理解。 +? 一个对象由于引用了其他很多对象并且直接和这些对象通信, +导致难以复用该对象。 +? 想通过一个中间类来封装多个类中的行为,而又不想生成太多 +的子类。可以通过引入中介者类来实现,在中介者中定义对象 +交互的公共行为,如果需要改变行为则可以增加新的中介者类。 + + +模式应用 +-------------------- +MVC架构中控制器 +Controller 作为一种中介者,它负责控制视图对象View和 +模型对象Model之间的交互。如在Struts中,Action就可以 +作为JSP页面与业务对象之间的中介者。 + + +模式扩展 +-------------------- +中介者模式与迪米特法则 +? 在中介者模式中,通过创造出一个中介者对象,将系 +统中有关的对象所引用的其他对象数目减少到最少, +使得一个对象与其同事之间的相互作用被这个对象与 +中介者对象之间的相互作用所取代。因此,中介者模 +式就是迪米特法则的一个典型应用。 + +中介者模式与GUI开发 +? 中介者模式可以方便地应用于图形界面(GUI)开发中,在 +比较复杂的界面中可能存在多个界面组件之间的交互关系。 +? 对于这些复杂的交互关系,有时候我们可以引入一个中介 +者类,将这些交互的组件作为具体的同事类,将它们之间 +的引用和控制关系交由中介者负责,在一定程度上简化系 +统的交互,这也是中介者模式的常见应用之一。 + + +总结 +-------------------- +中介者模式用一个中介对象来封装一系列的对象交互,中介者使各对 +象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变 +它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为 +型模式。 +? 中介者模式包含四个角色:抽象中介者用于定义一个接口,该接口用 +于与各同事对象之间的通信;具体中介者是抽象中介者的子类,通过 +协调各个同事对象来实现协作行为,了解并维护它的各个同事对象的 +引用;抽象同事类定义各同事的公有方法;具体同事类是抽象同事类 +的子类,每一个同事对象都引用一个中介者对象;每一个同事对象在 +需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完 +成与其他同事类的通信;在具体同事类中实现了在抽象同事类中定义 +的方法。 + +通过引入中介者对象,可以将系统的网状结构变成以中介者为中心的 +星形结构,中介者承担了中转作用和协调作用。中介者类是中介者模 +式的核心,它对整个系统进行控制和协调,简化了对象之间的交互, +还可以对对象间的交互进行进一步的控制。 +? 中介者模式的主要优点在于简化了对象之间的交互,将各同事解耦, +还可以减少子类生成,对于复杂的对象之间的交互,通过引入中介者, +可以简化各同事类的设计和实现;中介者模式主要缺点在于具体中介 +者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复 +杂,使得系统难以维护。 +? 中介者模式适用情况包括:系统中对象之间存在复杂的引用关系,产 +生的相互依赖关系结构混乱且难以理解;一个对象由于引用了其他很 +多对象并且直接和这些对象通信,导致难以复用该对象;想通过一个 +中间类来封装多个类中的行为,而又不想生成太多的子类。 + + diff --git a/behavioral_patterns/observer.rst b/behavioral_patterns/observer.rst new file mode 100644 index 0000000..c28e9f6 --- /dev/null +++ b/behavioral_patterns/observer.rst @@ -0,0 +1,157 @@ +.. _observer: + +观察者模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +建立一种对象与对象之间的依赖关系,一个对象发生改 +变时将自动通知其他对象,其他对象将相应做出反应。 +在此,发生改变的对象称为观察目标,而被通知的对象 +称为观察者,一个观察目标可以对应多个观察者,而且 +这些观察者之间没有相互联系,可以根据需要增加和删 +除观察者,使得系统更易于扩展,这就是观察者模式的 +模式动机。 + + +模式定义 +-------------------- +观察者模式(Observer Pattern):定义对象间的一种 +一对多依赖关系,使得每当一个对象状态发生改变时, +其相关依赖对象皆得到通知并被自动更新。观察者模式 +又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器 +(Source/Listener)模式或从属者(Dependents) +模式。观察者模式是一种对象行为型模式。 + + +模式结构 +-------------------- +观察者模式包含如下角色: +? Subject: 目标 +? ConcreteSubject: 具体目标 +? Observer: 观察者 +? ConcreteObserver: 具体观察者 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + + +模式分析 +-------------------- +观察者模式描述了如何建立对象与对象之间的依赖关系,如何 +构造满足这种需求的系统。 +?这一模式中的关键对象是观察目标和观察者,一个目标可以有 +任意数目的与之相依赖的观察者,一旦目标的状态发生改变, +所有的观察者都将得到通知。 +?作为对这个通知的响应,每个观察者都将即时更新自己的状态, +以与目标状态同步,这种交互也称为发布-订阅(publishsubscribe)。目标是通知的发布者,它发出通知时并不需要知 +道谁是它的观察者,可以有任意数目的观察者订阅它并接收通 +知。 + + +实例 +-------------------- + +优点 +-------------------- +观察者模式的优点 +? 观察者模式可以实现表示层和数据逻辑层的分离,并定义 +了稳定的消息更新传递机制,抽象了更新接口,使得可以 +有各种各样不同的表示层作为具体观察者角色。 +? 观察者模式在观察目标和观察者之间建立一个抽象的耦合。 +? 观察者模式支持广播通信。 +? 观察者模式符合“开闭原则”的要求。 + +缺点 +-------------------- +观察者模式的缺点 +? 如果一个观察目标对象有很多直接和间接的观察者的话, +将所有的观察者都通知到会花费很多时间。 +? 如果在观察者和观察目标之间有循环依赖的话,观察目标 +会触发它们之间进行循环调用,可能导致系统崩溃。 +? 观察者模式没有相应的机制让观察者知道所观察的目标对 +象是怎么发生变化的,而仅仅只是知道观察目标发生了变 +化。 + + +适用环境 +-------------------- +在以下情况下可以使用观察者模式: +? 一个抽象模型有两个方面,其中一个方面依赖于另一个方 +面。将这些方面封装在独立的对象中使它们可以各自独立 +地改变和复用。 +? 一个对象的改变将导致其他一个或多个对象也发生改变, +而不知道具体有多少对象将发生改变,可以降低对象之间 +的耦合度。 +? 一个对象必须通知其他对象,而并不知道这些对象是谁。 +? 需要在系统中创建一个触发链,A对象的行为将影响B对象, +B对象的行为将影响C对象……,可以使用观察者模式创建 +一种链式触发机制。 + + +模式应用 +-------------------- +观察者模式在软件开发中应用非常广泛,如某电子商务 +网站可以在执行发送操作后给用户多个发送商品打折信息, +某团队战斗游戏中某队友牺牲将给所有成员提示等等,凡是 +涉及到一对一或者一对多的对象交互场景都可以使用观察者 +模式。 + + +模式扩展 +-------------------- +MVC模式 +? MVC模式是一种架构模式,它包含三个角色:模型 +(Model),视图(View)和控制器(Controller)。观察者模式 +可以用来实现MVC模式,观察者模式中的观察目标就是 +MVC模式中的模型(Model),而观察者就是MVC中的视图 +(View),控制器(Controller)充当两者之间的中介者 +(Mediator)。当模型层的数据发生改变时,视图层将自 +动改变其显示内容。 + + +总结 +-------------------- +观察者模式定义对象间的一种一对多依赖关系,使得每当一个对象状 +态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模 +式又叫做发布-订阅模式、模型-视图模式、源-监听器模式或从属者模 +式。观察者模式是一种对象行为型模式。 +? 观察者模式包含四个角色:目标又称为主题,它是指被观察的对象; +具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它 +的状态发生改变时,向它的各个观察者发出通知;观察者将对观察目 +标的改变做出反应;在具体观察者中维护一个指向具体目标对象的引 +用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态 +保持一致。 +? 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监 +听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有 +观察者对象,使它们能够自动更新。 +? 观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并 +在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主 +要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话, +将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察 +目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用, +可能导致系统崩溃。 +? 观察者模式适用情况包括:一个抽象模型有两个方面,其中一个方面 +依赖于另一个方面;一个对象的改变将导致其他一个或多个对象也发 +生改变,而不知道具体有多少对象将发生改变;一个对象必须通知其 +他对象,而并不知道这些对象是谁;需要在系统中创建一个触发链。 +? 在JDK的java.util包中,提供了Observable类以及Observer接口,它们 +构成了Java语言对观察者模式的支持。 + + diff --git a/behavioral_patterns/state.rst b/behavioral_patterns/state.rst new file mode 100644 index 0000000..9212557 --- /dev/null +++ b/behavioral_patterns/state.rst @@ -0,0 +1,182 @@ +.. _state: + +状态模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +?在很多情况下,一个对象的行为取决于一个或多个动态 +变化的属性,这样的属性叫做状态,这样的对象叫做有 +状态的(stateful)对象,这样的对象状态是从事先定义 +好的一系列值中取出的。当一个这样的对象与外部事件 +产生互动时,其内部状态就会改变,从而使得系统的行 +为也随之发生变化。 +?在UML中可以使用状态图来描述对象状态的变化。 + + +模式定义 +-------------------- +状态模式(State Pattern) :允许一个对象在其内部状 +态改变时改变它的行为,对象看起来似乎修改了它的类。 +其别名为状态对象(Objects for States),状态模式是 +一种对象行为型模式。 + + +模式结构 +-------------------- +状态模式包含如下角色: +? Context: 环境类 +? State: 抽象状态类 +? ConcreteState: 具体状态类 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + + +模式分析 +-------------------- +状态模式描述了对象状态的变化以及对象如何在每一 +种状态下表现出不同的行为。 +?状态模式的关键是引入了一个抽象类来专门表示对象 +的状态,这个类我们叫做抽象状态类,而对象的每一 +种具体状态类都继承了该类,并在不同具体状态类中 +实现了不同状态的行为,包括各种状态之间的转换。 + +在状态模式结构中需要理解环境类与抽象状态类的作用: +? 环境类实际上就是拥有状态的对象,环境类有时候可以充当状态管理器 +(State Manager)的角色,可以在环境类中对状态进行切换操作。 +? 抽象状态类可以是抽象类,也可以是接口,不同状态类就是继承这个父类的 +不同子类,状态类的产生是由于环境类存在多个状态,同时还满足两个条件: +这些状态经常需要切换,在不同的状态下对象的行为不同。因此可以将不同 +对象下的行为单独提取出来封装在具体的状态类中,使得环境类对象在其内 +部状态改变时可以改变它的行为,对象看起来似乎修改了它的类,而实际上 +是由于切换到不同的具体状态类实现的。由于环境类可以设置为任一具体状 +态类,因此它针对抽象状态类进行编程,在程序运行时可以将任一具体状态 +类的对象设置到环境类中,从而使得环境类可以改变内部状态,并且改变行 +为。 + +实例 +-------------------- +TCPConnection: +示例来自《设计模式》 + +优点 +-------------------- +?状态模式的优点 +? 封装了转换规则。 +? 枚举可能的状态,在枚举状态之前需要确定状态种类。 +? 将所有与某个状态有关的行为放到一个类中,并且可以方便地 +增加新的状态,只需要改变对象状态即可改变对象的行为。 +? 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的 +条件语句块。 +? 可以让多个环境对象共享一个状态对象,从而减少系统中对象 +的个数。 + + + +缺点 +-------------------- +状态模式的缺点 +? 状态模式的使用必然会增加系统类和对象的个数。 +? 状态模式的结构与实现都较为复杂,如果使用不当将导致程序 +结构和代码的混乱。 +? 状态模式对“开闭原则”的支持并不太好,对于可以切换状态 +的状态模式,增加新的状态类需要修改那些负责状态转换的源 +代码,否则无法切换到新增状态;而且修改某个状态类的行为 +也需修改对应类的源代码。 + + +适用环境 +-------------------- +在以下情况下可以使用状态模式: +? 对象的行为依赖于它的状态(属性)并且可以根据它的状 +态改变而改变它的相关行为。 +? 代码中包含大量与对象状态有关的条件语句,这些条件语 +句的出现,会导致代码的可维护性和灵活性变差,不能方 +便地增加和删除状态,使客户类与类库之间的耦合增强。 +在这些条件语句中包含了对象的行为,而且这些条件对应 +于对象的各种状态。 + + +模式应用 +-------------------- +状态模式在工作流或游戏等类型的软件中得以广泛 +使用,甚至可以用于这些系统的核心功能设计,如在政 +府OA办公系统中,一个批文的状态有多种:尚未办理; +正在办理;正在批示;正在审核;已经完成等各种状态, +而且批文状态不同时对批文的操作也有所差异。使用状 +态模式可以描述工作流对象(如批文)的状态转换以及 +不同状态下它所具有的行为。 + +模式扩展 +-------------------- +共享状态 +? 在有些情况下多个环境对象需要共享同一个状态,如 +果希望在系统中实现多个环境对象实例共享一个或多 +个状态对象,那么需要将这些状态对象定义为环境的 +静态成员对象 + +简单状态模式与可切换状态的状态模式 +? (1) 简单状态模式:简单状态模式是指状态都相互独立,状态之 +间无须进行转换的状态模式,这是最简单的一种状态模式。对 +于这种状态模式,每个状态类都封装与状态相关的操作,而无 +须关心状态的切换,可以在客户端直接实例化状态类,然后将 +状态对象设置到环境类中。如果是这种简单的状态模式,它遵 +循“开闭原则”,在客户端可以针对抽象状态类进行编程,而 +将具体状态类写到配置文件中,同时增加新的状态类对原有系 +统也不造成任何影响 +2) 可切换状态的状态模式:大多数的状态模式都是可以切换状 +态的状态模式,在实现状态切换时,在具体状态类内部需要调 +用环境类Context的setState()方法进行状态的转换操作,在具体 +状态类中可以调用到环境类的方法,因此状态类与环境类之间 +通常还存在关联关系或者依赖关系。通过在状态类中引用环境 +类的对象来回调环境类的setState()方法实现状态的切换。在这 +种可以切换状态的状态模式中,增加新的状态类可能需要修改 +其他某些状态类甚至环境类的源代码,否则系统无法切换到新 +增状态。 + + +总结 +-------------------- +? 状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起 +来似乎修改了它的类。其别名为状态对象,状态模式是一种对象行为 +型模式。 +? 状态模式包含三个角色:环境类又称为上下文类,它是拥有状态的对 +象,在环境类中维护一个抽象状态类State的实例,这个实例定义当前 +状态,在具体实现时,它是一个State子类的对象,可以定义初始状态; +抽象状态类用于定义一个接口以封装与环境类的一个特定状态相关的 +行为;具体状态类是抽象状态类的子类,每一个子类实现一个与环境 +类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状 +态,不同的具体状态类其行为有所不同。 +? 状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出 +不同的行为。 +? 状态模式的主要优点在于封装了转换规则,并枚举可能的状态,它将 +所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的 +状态,只需要改变对象状态即可改变对象的行为,还可以让多个环境 +对象共享一个状态对象,从而减少系统中对象的个数;其缺点在于使 +用状态模式会增加系统类和对象的个数,且状态模式的结构与实现都 +较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切 +换状态的状态模式不满足“开闭原则”的要求。 +? 状态模式适用情况包括:对象的行为依赖于它的状态(属性)并且可 +以根据它的状态改变而改变它的相关行为;代码中包含大量与对象状 +态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和 +灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦 +合增强。 + + diff --git a/behavioral_patterns/strategy.rst b/behavioral_patterns/strategy.rst new file mode 100644 index 0000000..be97150 --- /dev/null +++ b/behavioral_patterns/strategy.rst @@ -0,0 +1,164 @@ +.. _strategy: + +策略模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +- 完成一项任务,往往可以有多种不同的方式,每一种方 +式称为一个策略,我们可以根据环境或者条件的不同选 +择不同的策略来完成该项任务。 +?在软件开发中也常常遇到类似的情况,实现某一个功能 +有多个途径,此时可以使用一种设计模式来使得系统可 +以灵活地选择解决途径,也能够方便地增加新的解决途 +径。 +- 在软件系统中,有许多算法可以实现某一功能,如查找、排序等, +一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供 +多种查找算法,可以将这些算法写到一个类中,在该类中提供多个 +方法,每一个方法对应一个具体的查找算法;当然也可以将这些查 +找算法封装在一个统一的方法中,通过if…else…等条件判断语句 +来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要 +增加一种新的查找算法,需要修改封装算法类的源代码;更换查找 +算法,也需要修改客户端调用代码。在这个算法类中封装了大量查 +找算法,该类代码将较复杂,维护较为困难。 +- 除了提供专门的查找算法类之外,还可以在客户端程序中直接包含 +算法代码,这种做法更不可取,将导致客户端程序庞大而且难以维 +护,如果存在大量可供选择的算法时问题将变得更加严重。 +- 为了解决这些问题,可以定义一些独立的类来封装不同的算法,每 +一个类封装一个具体的算法,在这里,每一个封装算法的类我们都 +可以称之为策略(Strategy),为了保证这些策略的一致性,一般会 +用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一 +个具体策略类。 + + +模式定义 +-------------------- +策略模式(Strategy Pattern):定义一系列算法,将每 +一个算法封装起来,并让它们可以相互替换。策略模式 +让算法独立于使用它的客户而变化,也称为政策模式 +(Policy)。策略模式是一种对象行为型模式。 + + +模式结构 +-------------------- +策略模式包含如下角色: +? Context: 环境类 +? Strategy: 抽象策略类 +? ConcreteStrategy: 具体策略类 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + +模式分析 +-------------------- +策略模式是一个比较容易理解和使用的设计模式,策 +略模式是对算法的封装,它把算法的责任和算法本身 +分割开,委派给不同的对象管理。策略模式通常把一 +个系列的算法封装到一系列的策略类里面,作为一个 +抽象策略类的子类。用一句话来说,就是“准备一组 +算法,并将每一个算法封装起来,使得它们可以互 +换”。 + +在策略模式中,应当由客户端自己决定在什么情况下使用什么 +具体策略角色。 +?策略模式仅仅封装算法,提供新算法插入到已有系统中,以及 +老算法从系统中“退休”的方便,策略模式并不决定在何时使 +用何种算法,算法的选择由客户端来决定。这在一定程度上提 +高了系统的灵活性,但是客户端需要理解所有具体策略类之间 +的区别,以便选择合适的算法,这也是策略模式的缺点之一, +在一定程度上增加了客户端的使用难度。 + +实例 +-------------------- + +优点 +-------------------- +策略模式的优点 +? 策略模式提供了对“开闭原则”的完美支持,用户可以在不修 +改原有系统的基础上选择算法或行为,也可以灵活地增加新的 +算法或行为。 +? 策略模式提供了管理相关的算法族的办法。 +? 策略模式提供了可以替换继承关系的办法。 +? 使用策略模式可以避免使用多重条件转移语句。 + + +缺点 +-------------------- +策略模式的缺点 +? 客户端必须知道所有的策略类,并自行决定使用哪一 +个策略类。 +? 策略模式将造成产生很多策略类,可以通过使用享元 +模式在一定程度上减少对象的数量。 + + +适用环境 +-------------------- +在以下情况下可以使用策略模式: +? 如果在一个系统里面有许多类,它们之间的区别仅在于它们的 +行为,那么使用策略模式可以动态地让一个对象在许多行为中 +选择一种行为。 +? 一个系统需要动态地在几种算法中选择一种。 +? 如果一个对象有很多的行为,如果不用恰当的模式,这些行为 +就只好使用多重的条件选择语句来实现。 +? 不希望客户端知道复杂的、与算法相关的数据结构,在具体策 +略类中封装算法和相关的数据结构,提高算法的保密性与安全 +性 + + +模式应用 +-------------------- + +模式扩展 +-------------------- +策略模式与状态模式 +? 可以通过环境类状态的个数来决定是使用策略模式还是状态模式。 +? 策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环 +境类;而状态模式的环境类由于外在因素需要放进一个具体状态中, +以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种 +双向的关联关系。 +? 使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用 +状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的 +操作自动转换。 +? 如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而 +且这些状态之间可以发生转换时使用状态模式;如果系统中某个类的 +某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略 +模式。 + + +总结 +-------------------- +- 在策略模式中定义了一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式。策略模式是一种对象行为型模式。 +- 策略模式包含三个角色:环境类在解决某个问题时可以采用多种 +策略,在环境类中维护一个对抽象策略类的引用实例;抽象策略 +类为所支持的算法声明了抽象方法,是所有策略类的父类;具体 +策略类实现了在抽象策略类中定义的算法。 +- 策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。 +- 策略模式主要优点在于对“开闭原则”的完美支持,在不 +修改原有系统的基础上可以更换算法或者增加新的算法, +它很好地管理算法族,提高了代码的复用性,是一种替换 +继承,避免多重条件转移语句的实现方式;其缺点在于客 +户端必须知道所有的策略类,并理解其区别,同时在一定 +程度上增加了系统中类的个数,可能会存在很多策略类。 +- 策略模式适用情况包括:在一个系统里面有许多类,它们 +之间的区别仅在于它们的行为,使用策略模式可以动态地 +让一个对象在许多行为中选择一种行为;一个系统需要动 +态地在几种算法中选择一种;避免使用难以维护的多重条 +件选择语句;希望在具体策略类中封装算法和与相关的数 +据结构。 + diff --git a/code/AbstractFactory/ConcreteFactory1.cpp b/code/AbstractFactory/ConcreteFactory1.cpp index 537cb8a..8b6c52f 100644 --- a/code/AbstractFactory/ConcreteFactory1.cpp +++ b/code/AbstractFactory/ConcreteFactory1.cpp @@ -14,16 +14,10 @@ ConcreteFactory1::ConcreteFactory1(){ } - - ConcreteFactory1::~ConcreteFactory1(){ } - - - - AbstractProductA * ConcreteFactory1::createProductA(){ return new ProductA1(); } diff --git a/code/AbstractFactory/ConcreteFactory2.cpp b/code/AbstractFactory/ConcreteFactory2.cpp index 77a37df..5d23f4d 100644 --- a/code/AbstractFactory/ConcreteFactory2.cpp +++ b/code/AbstractFactory/ConcreteFactory2.cpp @@ -13,8 +13,6 @@ ConcreteFactory2::ConcreteFactory2(){ } - - ConcreteFactory2::~ConcreteFactory2(){ } diff --git a/code/AbstractFactory/main.cfp b/code/AbstractFactory/main.cfp index 370ea58..4181432 100644 --- a/code/AbstractFactory/main.cfp +++ b/code/AbstractFactory/main.cfp @@ -28,21 +28,21 @@ ProductB1.h ProductB2.cpp ProductB2.h -main.cpp +main.cpp AbstractFactory.cpp AbstractFactory.h AbstractProductA.cpp AbstractProductA.h AbstractProductB.cpp AbstractProductB.h -ConcreteFactory1.cpp -ConcreteFactory1.h -ConcreteFactory2.cpp +ConcreteFactory1.cpp +ConcreteFactory1.h +ConcreteFactory2.cpp ConcreteFactory2.h -ProductA1.cpp -ProductA1.h +ProductA1.cpp +ProductA1.h ProductA2.cpp -ProductB1.cpp +ProductB1.cpp ProductB2.cpp 111 mingw5 diff --git a/code/Builder/Director.cpp b/code/Builder/Director.cpp index 05bf2a3..4b1fce3 100644 --- a/code/Builder/Director.cpp +++ b/code/Builder/Director.cpp @@ -7,21 +7,12 @@ #include "Director.h" - Director::Director(){ - } - - Director::~Director(){ - } - - - - Product* Director::constuct(){ m_pbuilder->buildPartA(); m_pbuilder->buildPartB(); diff --git a/code/Builder/main.cfp b/code/Builder/main.cfp index 11027bc..aef7b91 100644 --- a/code/Builder/main.cfp +++ b/code/Builder/main.cfp @@ -18,12 +18,12 @@ Director.h Product.cpp Product.h -main.cpp -Builder.cpp +main.cpp +Builder.cpp Builder.h -ConcreteBuilder.cpp +ConcreteBuilder.cpp ConcreteBuilder.h -Director.cpp +Director.cpp Director.h Product.cpp Product.h diff --git a/code/Builder/mingw5/Director.o b/code/Builder/mingw5/Director.o index 5fc5ea3..ac69d06 100644 Binary files a/code/Builder/mingw5/Director.o and b/code/Builder/mingw5/Director.o differ diff --git a/code/Builder/mingw5/main.exe b/code/Builder/mingw5/main.exe index 6a5ae16..0cf2a69 100644 Binary files a/code/Builder/mingw5/main.exe and b/code/Builder/mingw5/main.exe differ diff --git a/code/Singleton/main.cfp b/code/Singleton/main.cfp index 92ed091..af33829 100644 --- a/code/Singleton/main.cfp +++ b/code/Singleton/main.cfp @@ -12,8 +12,8 @@ Singleton.cpp Singleton.h -main.cpp -Singleton.cpp +main.cpp +Singleton.cpp Singleton.h 111 mingw5 diff --git a/creational_patterns/abstract_factory.rst b/creational_patterns/abstract_factory.rst index 6701b84..6bc1c3b 100644 --- a/creational_patterns/abstract_factory.rst +++ b/creational_patterns/abstract_factory.rst @@ -3,36 +3,20 @@ 抽象工厂模式(Abstract Factory) ======================================== +.. contents:: 目录 + 模式动机 -------------------- -- 在工厂方法模式中具体工厂负责生产具体的产品,每一 -个具体工厂对应一种具体产品,工厂方法也具有唯一性, -一般情况下,一个具体工厂中只有一个工厂方法或者一 -组重载的工厂方法。但是有时候我们需要一个工厂可以 -提供多个产品对象,而不是单一的产品对象。 +- 在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。 为了更清晰地理解工厂方法模式,需要先引入两个概念: - * 产品等级结构:产品等级结构即产品的继承结构,如一个抽象 - 类是电视机,其子类有海尔电视机、海信电视机、TCL电视机, - 则抽象电视机与具体品牌的电视机之间构成了一个产品等级结 - 构,抽象电视机是父类,而具体品牌的电视机是其子类。 - * 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的, - 位于不同产品等级结构中的一组产品,如海尔电器工厂生产的 - 海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结 - 构中,海尔电冰箱位于电冰箱产品等级结构中。 - -- 当系统所提供的工厂所需生产的具体产品并不是一个简单的对象, -而是多个位于不同产品等级结构中属于不同类型的具体产品时需要 -使用抽象工厂模式。 -- 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一 -种形态。 -- 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对 -的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级 -结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品 -对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等 -级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模 -式更为简单、有效率 + * **产品等级结构** :产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。 + * **产品族** :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。 + +- 当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式。 +- 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。 +- 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。 模式定义 @@ -58,12 +42,27 @@ 代码分析 -------------------- -.. literalinclude:: /code/SimpleFactory/Factory.cpp +.. literalinclude:: /code/AbstractFactory/main.cpp + :language: cpp + :linenos: + :lines: 1-21 + :emphasize-lines: 11-21 + +.. literalinclude:: /code/AbstractFactory/ConcreteFactory1.cpp + :language: cpp + :linenos: + :lines: 1-10,21-28 + :emphasize-lines: 11-18 + +.. literalinclude:: /code/AbstractFactory/ProductA1.cpp :language: cpp :linenos: - :lines: 1-10,24- - :emphasize-lines: 12-19 + :lines: 1-10,21-23 + :emphasize-lines: 11-13 +运行结果: + +.. image:: /_static/AbatractFactory_run.jpg 模式分析 @@ -74,20 +73,52 @@ 优点 -------------------- +- 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。 +- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。 +- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。 + 缺点 -------------------- +- 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。 +- 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。 + 适用环境 -------------------- +在以下情况下可以使用抽象工厂模式: + +- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。 +- 系统中有多于一个的产品族,而每次只使用其中某一产品族。 +- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。 +- 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。 + 模式应用 -------------------- +在很多软件系统中需要更换界面主题,要求界面中的按钮、文本框、背景色等一起发生改变时,可以使用抽象工厂模式进行设计。 + 模式扩展 -------------------- +“开闭原则”的倾斜性 +^^^^^^^^^^^^^^^^^^^^^ + +- “开闭原则”要求系统对扩展开放,对修改封闭,通过扩展达到增强其功能的目的。对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两方面: + (1) 增加产品族:对于增加新的产品族,工厂方法模式很好的支持了“开闭原则”,对于新增加的产品族,只需要对应增加一个新的具体工厂即可,对已有代码无须做任何修改。 + (2) 增加新的产品等级结构:对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,不能很好地支持“开闭原则”。 +- 抽象工厂模式的这种性质称为“开闭原则”的倾斜性,抽象工厂模式以一种倾斜的方式支持增加新的产品,它为新产品族的增加提供方便,但不能为新的产品等级结构的增加提供这样的方便。 + +工厂模式的退化 +^^^^^^^^^^^^^^^^^^^^ +- 当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。 总结 -------------------- +- 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。 +- 抽象工厂模式包含四个角色:抽象工厂用于声明生成抽象产品的方法;具体工厂实现了抽象工厂声明的生成抽象产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中;抽象产品为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法;具体产品定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。 +- 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。 +- 抽象工厂模式的主要优点是隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;主要缺点在于增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。 +- 抽象工厂模式适用情况包括:一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节;系统中有多于一个的产品族,而每次只使用其中某一产品族;属于同一个产品族的产品将在一起使用;系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。 diff --git a/creational_patterns/builder.rst b/creational_patterns/builder.rst index 808804b..dd64d46 100644 --- a/creational_patterns/builder.rst +++ b/creational_patterns/builder.rst @@ -3,6 +3,8 @@ 建造者模式 ==================== +.. contents:: 目录 + 模式动机 -------------------- 无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。 @@ -38,12 +40,25 @@ 代码分析 -------------------- -.. literalinclude:: /code/SimpleFactory/Factory.cpp +.. literalinclude:: /code/Builder/main.cpp :language: cpp :linenos: :lines: 1- - :emphasize-lines: 12-19 + :emphasize-lines: 11-15 + +.. literalinclude:: /code/Builder/ConcreteBuilder.cpp + :language: cpp + :linenos: + :emphasize-lines: 21-33 + +.. literalinclude:: /code/Builder/Director.cpp + :language: cpp + :linenos: + :emphasize-lines: 16-21 + +运行结果: +.. image:: /_static/Builder_run.jpg 模式分析 @@ -114,8 +129,7 @@ Builder角色扮演指挥者与建造者双重角色。 - 建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。 - 建造者模式包含如下四个角色:抽象建造者为创建一个产品对象的各个部件指定抽象接口;具体建造者实现了抽象建造者接口,实现各个部件的构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象;产品角色是被构建的复杂对象,包含多个组成部件;指挥者负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造 - 在建造者模式的结构中引入了一个指挥者类,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。 -- 建造者模式的主要优点在于客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,符合“开闭原则”,还可以更加精细地控制产品的创建过程;其主要缺点在于由于建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,因此其使用范围受到一定的限制,如果产品 -的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。 +- 建造者模式的主要优点在于客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,符合“开闭原则”,还可以更加精细地控制产品的创建过程;其主要缺点在于由于建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,因此其使用范围受到一定的限制,如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。 - 建造者模式适用情况包括:需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性;需要生成的产品对象的属性相互依赖,需要指定其生成顺序;对象的创建过程独立于创建该对象的类;隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同类型的产品。 diff --git a/creational_patterns/creational.rst b/creational_patterns/creational.rst index 0d5fe3e..0c52951 100644 --- a/creational_patterns/creational.rst +++ b/creational_patterns/creational.rst @@ -31,5 +31,8 @@ simple_factory factory_method + abstract_factory + builder + singleton diff --git a/creational_patterns/singleton.rst b/creational_patterns/singleton.rst index e347194..36d1626 100644 --- a/creational_patterns/singleton.rst +++ b/creational_patterns/singleton.rst @@ -3,32 +3,22 @@ 单例模式 ==================== +.. contents:: 目录 + 模式动机 -------------------- -对于系统中的某些类来说,只有一个实例很重要,例如,一个系统 -中可以存在多个打印任务,但是只能有一个正在工作的任务;一个 -系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时 -工具或ID(序号)生成器。 +对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。 -如何保证一个类只有一个实例并且这个实例易于被访问 -呢?定义一个全局变量可以确保对象随时都可以被访问, -但不能防止我们实例化多个对象。 +如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。 -一个更好的解决办法是让类自身负责保存它的唯一实例。 -这个类可以保证没有其他实例被创建,并且它可以提供 -一个访问该实例的方法。这就是单例模式的模式动机。 +一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。 模式定义 -------------------- -单例模式(Singleton Pattern):单例模式确保某一个 -类只有一个实例,而且自行实例化并向整个系统提供这 -个实例,这个类称为单例类,它提供全局访问的方法。 +单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。 -单例模式的要点有三个:一是某个类只能有一个实例; -二是它必须自行创建这个实例;三是它必须自行向整个 -系统提供这个实例。单例模式是一种对象创建型模式。 -单例模式又名单件模式或单态模式。 +单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。 模式结构 @@ -46,24 +36,23 @@ 代码分析 -------------------- -.. literalinclude:: /code/SimpleFactory/Factory.cpp +.. literalinclude:: /code/Singleton/main.cpp + :language: cpp + :linenos: + :emphasize-lines: 7-8 + +.. literalinclude:: /code/Singleton/Singleton.cpp :language: cpp :linenos: - :lines: 1-10,24- - :emphasize-lines: 12-19 + :emphasize-lines: 21-28 +运行结果: +.. image:: /_static/Singleton_run.jpg 模式分析 -------------------- -单例模式的目的是保证一个类仅有一个实例,并提供一 -个访问它的全局访问点。单例模式包含的角色只有一个, -就是单例类——Singleton。单例类拥有一个私有构造 -函数,确保用户无法通过new关键字直接实例化它。除 -此之外,该模式中包含一个静态私有成员变量与静态公 -有的工厂方法,该工厂方法负责检验实例的存在性并实 -例化自己,然后存储在静态成员变量中,以确保只有一 -个实例被创建。 +单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色只有一个,就是单例类——Singleton。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。 在单例模式的实现过程中,需要注意如下三点: @@ -74,58 +63,35 @@ 实例 -------------------- -在操作系统中,打印池(Print Spooler)是一个用于管理打印任务 -的应用程序,通过打印池用户可以删除、中止或者改变打印任 -务的优先级,在一个系统中只允许运行一个打印池对象,如果 -重复创建打印池则抛出异常。现使用单例模式来模拟实现打印 -池的设计 +在操作系统中,打印池(Print Spooler)是一个用于管理打印任务的应用程序,通过打印池用户可以删除、中止或者改变打印任务的优先级,在一个系统中只允许运行一个打印池对象,如果重复创建打印池则抛出异常。现使用单例模式来模拟实现打印池的设计。 优点 -------------------- -- 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实 -例,所以它可以严格控制客户怎样以及何时访问它,并为设计 -及开发团队提供了共享的概念。 -- 由于在系统内存中只存在一个对象,因此可以节约系统资源, -对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高 -系统的性能。 -- 允许可变数目的实例。我们可以基于单例模式进行扩展,使用 -与单例控制相似的方法来获得指定个数的对象实例。 +- 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。 +- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。 +- 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。 缺点 -------------------- - 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。 -- 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单 -例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色, -包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。 -- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象 -设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池 -溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动 -垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会 -认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化, -这将导致对象状态的丢失 +- 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。 +- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。 适用环境 -------------------- 在以下情况下可以使用单例模式: -- 系统只需要一个实例对象,如系统要求提供一个唯一的序列号 -生成器,或者需要考虑资源消耗太大而只允许创建一个对象。 -- 客户调用类的单个实例只允许使用一个公共访问点,除了该公 -共访问点,不能通过其他途径访问该实例。 -- 在一个系统中要求一个类只有一个实例时才应当使用单例模式。 -反过来,如果一个类可以有几个实例共存,就需要对单例模式 -进行改进,使之成为多例模式 +- 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。 +- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。 +- 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,使之成为多例模式 模式应用 -------------------- -一个具有自动编号主键的表可以有多个用户同时使 -用,但数据库中只能有一个地方分配下一个主键编号, -否则会出现主键重复,因此该主键编号生成器必须具备 -唯一性,可以通过单例模式来实现。 +一个具有自动编号主键的表可以有多个用户同时使用,但数据库中只能有一个地方分配下一个主键编号,否则会出现主键重复,因此该主键编号生成器必须具备唯一性,可以通过单例模式来实现。 模式扩展 @@ -133,27 +99,11 @@ 总结 -------------------- -- 单例模式确保某一个类只有一个实例,而且自行实例化并 -向整个系统提供这个实例,这个类称为单例类,它提供全 -局访问的方法。单例模式的要点有三个:一是某个类只能 -有一个实例;二是它必须自行创建这个实例;三是它必须 -自行向整个系统提供这个实例。单例模式是一种对象创建 -型模式。 -- 单例模式只包含一个单例角色:在单例类的内部实现只生 -成一个实例,同时它提供一个静态的工厂方法,让客户可 -以使用它的唯一实例;为了防止在外部对其实例化,将其 -构造函数设计为私有。 -- 单例模式的目的是保证一个类仅有一个实例,并提供一个 -访问它的全局访问点。单例类拥有一个私有构造函数,确 -保用户无法通过new关键字直接实例化它。除此之外,该 -模式中包含一个静态私有成员变量与静态公有的工厂方法。 -该工厂方法负责检验实例的存在性并实例化自己,然后存 -储在静态成员变量中,以确保只有一个实例被创建。 -- 单例模式的主要优点在于提供了对唯一实例的受控访问并 -可以节约系统资源;其主要缺点在于因为缺少抽象层而难 -以扩展,且单例类职责过重。 -- 单例模式适用情况包括:系统只需要一个实例对象;客户 -调用类的单个实例只允许使用一个公共访问点。 +- 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。 +- 单例模式只包含一个单例角色:在单例类的内部实现只生成一个实例,同时它提供一个静态的工厂方法,让客户可以使用它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有。 +- 单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法。该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。 +- 单例模式的主要优点在于提供了对唯一实例的受控访问并可以节约系统资源;其主要缺点在于因为缺少抽象层而难以扩展,且单例类职责过重。 +- 单例模式适用情况包括:系统只需要一个实例对象;客户调用类的单个实例只允许使用一个公共访问点。 diff --git a/index.rst b/index.rst index c0d67a1..68bff74 100644 --- a/index.rst +++ b/index.rst @@ -25,7 +25,7 @@ -------------------- - +ref: 《java设计模式》的课件; Indices and tables ================== diff --git a/structural_patterns/adapter.rst b/structural_patterns/adapter.rst new file mode 100644 index 0000000..543fc2e --- /dev/null +++ b/structural_patterns/adapter.rst @@ -0,0 +1,170 @@ +.. _adapter: + +适配器模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +- 在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配 +器模式。 +- 通常情况下,客户端可以通过目标类的接口访问它所提供的服务。 +有时,现有的类可以满足客户类的功能需要,但是它所提供的接口 +不一定是客户类所期望的,这可能是因为现有类中方法名与目标类 +中定义的方法名不一致等原因所导致的。 +- 在这种情况下,现有的接口需要转化为客户类期望的接口,这样保 +证了对现有类的重用。如果不进行这样的转化,客户类就不能利用 +现有类所提供的功能,适配器模式可以完成这样的转化。 +- 在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这 +个包装类指的就是适配器(Adapter),它所包装的对象就是适配者 +(Adaptee),即被适配的类。 +- 适配器提供客户类需要的接口,适配器的实现就是把客户类的请求 +转化为对适配者的相应接口的调用。也就是说:当客户类调用适配 +器的方法时,在适配器类的内部将调用适配者类的方法,而这个过 +程对客户类是透明的,客户类并不直接访问适配者类。因此,适配 +器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适 +配器模式的模式动机。 + +模式定义 +-------------------- +适配器模式(Adapter Pattern) :将一个接口转换成客 +户希望的另一个接口,适配器模式使接口不兼容的那些 +类可以一起工作,其别名为包装器(Wrapper)。适配器 +模式既可以作为类结构型模式,也可以作为对象结构型 +模式。 + + + +模式结构 +-------------------- +适配器模式包含如下角色: + +- Target:目标抽象类 +- Adapter:适配器类 +- Adaptee:适配者类 +- Client:客户类 + +适配器模式有对象适配器和类适配器两种实现: + +对象适配器: + +类适配器: +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + +模式分析 +-------------------- + +实例 +-------------------- + +优点 +-------------------- +- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有 +的适配者类,而无须修改原有代码。 +- 增加了类的透明性和复用性,将具体的实现封装在适配者类中, +对于客户端类来说是透明的,而且提高了适配者的复用性。 +- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地 +更换适配器,也可以在不修改原有代码的基础上增加新的适配 +器类,完全符合“开闭原则”。 + +类适配器模式还具有如下优点: + 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。 + +对象适配器模式还具有如下优点: + 一个对象适配器可以把多个不同的适配者适配到同一个目标, +也就是说,同一个适配器可以把适配者类和它的子类都适配到 +目标接口。 + + + +缺点 +-------------------- +类适配器模式的缺点如下: + 对于Java、C#等不支持多重继承的语言,一次最多只能适配一 +个适配者类,而且目标抽象类只能为抽象类,不能为具体类, +其使用有一定的局限性,不能将一个适配者类和它的子类都适 +配到目标接口。 + +对象适配器模式的缺点如下: + 与类适配器模式相比,要想置换适配者类的方法就不容易。如 +果一定要置换掉适配者类的一个或多个方法,就只好先做一个 +适配者类的子类,将适配者类的方法置换掉,然后再把适配者 +类的子类当做真正的适配者进行适配,实现过程较为复杂。 + + +适用环境 +-------------------- +在以下情况下可以使用适配器模式: + +- 系统需要使用现有的类,而这些类的接口不符合系统 +的需要。 +- 想要建立一个可以重复使用的类,用于与一些彼此之 +间没有太大关联的一些类,包括一些可能在将来引进 +的类一起工作。 + + +模式应用 +-------------------- +Sun公司在1996年公开了Java语言的数据库连接 +工具JDBC,JDBC使得Java语言程序能够与数据库连 +接,并使用SQL语言来查询和操作数据。JDBC给出一 +个客户端通用的抽象接口,每一个具体数据库引擎(如 +SQL Server、Oracle、MySQL等)的JDBC驱动软件 +都是一个介于JDBC接口和数据库引擎接口之间的适配 +器软件。抽象的JDBC接口和各个数据库引擎API之间 +都需要相应的适配器软件,这就是为各个不同数据库引 +擎准备的驱动程序。 + + +模式扩展 +-------------------- +认适配器模式(Default Adapter Pattern)或缺省适 +配器模式 + 当不需要全部实现接口提供的方法时,可先设计一个抽象 +类实现接口,并为该接口中每个方法提供一个默认实现 +(空方法),那么该抽象类的子类可有选择地覆盖父类的 +某些方法来实现需求,它适用于一个接口不想使用其所有 +的方法的情况。因此也称为单接口适配器模式。 + + +总结 +-------------------- +- 结构型模式描述如何将类或者对象结合在一起形成更大的结构。 +- 适配器模式用于将一个接口转换成客户希望的另一个接口,适配器模 +式使接口不兼容的那些类可以一起工作,其别名为包装器。适配器模 +式既可以作为类结构型模式,也可以作为对象结构型模式。 +- 适配器模式包含四个角色:目标抽象类定义客户要用的特定领域的接 +口;适配器类可以调用另一个接口,作为一个转换器,对适配者和抽 +象目标类进行适配,它是适配器模式的核心;适配者类是被适配的角 +色,它定义了一个已经存在的接口,这个接口需要适配;在客户类中 +针对目标抽象类进行编程,调用在目标抽象类中定义的业务方法。 +- 在类适配器模式中,适配器类实现了目标抽象类接口并继承了适配者 +类,并在目标抽象类的实现方法中调用所继承的适配者类的方法;在 +对象适配器模式中,适配器类继承了目标抽象类并定义了一个适配者 +类的对象实例,在所继承的目标抽象类方法中调用适配者类的相应业 +务方法。 +- 适配器模式的主要优点是将目标类和适配者类解耦,增加 +了类的透明性和复用性,同时系统的灵活性和扩展性都非 +常好,更换适配器或者增加新的适配器都非常方便,符合 +“开闭原则”;类适配器模式的缺点是适配器类在很多编 +程语言中不能同时适配多个适配者类,对象适配器模式的 +缺点是很难置换适配者类的方法。 +- 适配器模式适用情况包括:系统需要使用现有的类,而这 +些类的接口不符合系统的需要;想要建立一个可以重复使 +用的类,用于与一些彼此之间没有太大关联的一些类一起 +工作。 diff --git a/structural_patterns/bridge.rst b/structural_patterns/bridge.rst new file mode 100644 index 0000000..11f96ab --- /dev/null +++ b/structural_patterns/bridge.rst @@ -0,0 +1,176 @@ +.. _bridge: + +桥接模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +设想如果要绘制矩形、圆形、椭圆、正方形,我们至少 +需要4个形状类,但是如果绘制的图形需要具有不同的 +颜色,如红色、绿色、蓝色等,此时至少有如下两种设 +计方案: + +- 第一种设计方案是为每一种形状都提供一套各种颜色的版本。 +- 第二种设计方案是根据实际需要对形状和颜色进行组合 + +对于有两个变化维度(即两个变化的原因)的 +系统,采用方案二来进行设计系统中类的个数 +更少,且系统扩展更为方便。设计方案二即是 +桥接模式的应用。桥接模式将继承关系转换为 +关联关系,从而降低了类与类之间的耦合,减 +少了代码编写量。 + + +模式定义 +-------------------- +桥接模式(Bridge Pattern):将抽象部分与它的实现部 +分分离,使它们都可以独立地变化。它是一种对象结构 +型模式,又称为柄体(Handle and Body)模式或接口 +(Interface)模式。 + + +模式结构 +-------------------- +桥接模式包含如下角色: + +- Abstraction:抽象类 +- RefinedAbstraction:扩充抽象类 +- Implementor:实现类接口 +- ConcreteImplementor:具体实现类 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + + +模式分析 +-------------------- +理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化 +(Implementation)脱耦,使得二者可以独立地变化。 + +- 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体 +对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即 +为抽象化的过程。 +- 实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现 +化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽 +象化事物的进一步具体化的产物。 +- 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将 +它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为 +关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象 +化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关 +系,从而使两者可以相对独立地变化,这就是桥接模式的用意。 + + +实例 +-------------------- +如果需要开发一个跨平台视频播放器,可以在不同操 +作系统平台(如Windows、Linux、Unix等)上播放 +多种格式的视频文件,常见的视频格式包括MPEG、 +RMVB、AVI、WMV等。现使用桥接模式设计该播放 +器。 + + +优点 +-------------------- +桥接模式的优点 +? 分离抽象接口及其实现部分。 +? 桥接模式有时类似于多继承方案,但是多继承方案违背了类的 +单一职责原则(即一个类只有一个变化的原因),复用性比较 +差,而且多继承结构中类的个数非常庞大,桥接模式是比多继 +承方案更好的解决方法。 +? 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展 +一个维度,都不需要修改原有系统。 +? 实现细节对客户透明,可以对用户隐藏实现细节。 + + +缺点 +-------------------- +桥接模式的缺点 +? 桥接模式的引入会增加系统的理解与设计难度,由于 +聚合关联关系建立在抽象层,要求开发者针对抽象进 +行设计与编程。 +? 桥接模式要求正确识别出系统中两个独立变化的维度, +因此其使用范围具有一定的局限性。 + + +适用环境 +-------------------- +在以下情况下可以使用桥接模式: +? 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的 +灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可 +以使它们在抽象层建立一个关联关系。 +? 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在 +程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对 +象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。 +? 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。 +? 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化 +角色需要独立变化,设计要求需要独立管理这两者。 +? 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增 +加的系统,桥接模式尤为适用。 + + +模式应用 +-------------------- +一个Java桌面软件总是带有所在操作系统的视感 +(LookAndFeel),如果一个Java软件是在Unix系统上开发的, +那么开发人员看到的是Motif用户界面的视感;在Windows上面 +使用这个系统的用户看到的是Windows用户界面的视感;而一 +个在Macintosh上面使用的用户看到的则是Macintosh用户界面 +的视感,Java语言是通过所谓的Peer架构做到这一点的。Java +为AWT中的每一个GUI构件都提供了一个Peer构件,在AWT中 +的Peer架构就使用了桥接模式 + + +模式扩展 +-------------------- +适配器模式与桥接模式的联用 +? 桥接模式和适配器模式用于设计的不同阶段,桥接模式用于系 +统的初步设计,对于存在两个独立变化维度的类可以将其分为 +抽象化和实现化两个角色,使它们可以分别进行变化;而在初 +步设计完成之后,当发现系统与已有类无法协同工作时,可以 +采用适配器模式。但有时候在设计初期也需要考虑适配器模式, +特别是那些涉及到大量第三方应用接口的情况 + + +总结 +-------------------- +- 桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。 +它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口 +(Interface)模式。 +- 桥接模式包含如下四个角色:抽象类中定义了一个实现类接口类型的 +对象并可以维护该对象;扩充抽象类扩充由抽象类定义的接口,它实 +现了在抽象类中定义的抽象业务方法,在扩充抽象类中可以调用在实 +现类接口中定义的业务方法;实现类接口定义了实现类的接口,实现 +类接口仅提供基本操作,而抽象类定义的接口可能会做更多更复杂的 +操作;具体实现类实现了实现类接口并且具体实现它,在不同的具体 +实现类中提供基本操作的不同实现,在程序运行时,具体实现类对象 +将替换其父类对象,提供给客户端具体的业务操作方法。 +- 在桥接模式中,抽象化(Abstraction)与实现化(Implementation)脱耦, +它们可以沿着各自的维度独立变化。 +- 桥接模式的主要优点是分离抽象接口及其实现部分,是比多继承方案 +更好的解决方法,桥接模式还提高了系统的可扩充性,在两个变化维 +度中任意扩展一个维度,都不需要修改原有系统,实现细节对客户透 +明,可以对用户隐藏实现细节;其主要缺点是增加系统的理解与设计 +难度,且识别出系统中两个独立变化的维度并不是一件容易的事情。 +- 桥接模式适用情况包括:需要在构件的抽象化角色和具体化角色之间 +增加更多的灵活性,避免在两个层次之间建立静态的继承联系;抽象 +化角色和实现化角色可以以继承的方式独立扩展而互不影响;一个类 +存在两个独立变化的维度,且这两个维度都需要进行扩展;设计要求 +需要独立管理抽象化角色和具体化角色;不希望使用继承或因为多层 +次继承导致系统类的个数急剧增加的系统。 + diff --git a/structural_patterns/decorator.rst b/structural_patterns/decorator.rst new file mode 100644 index 0000000..e48da66 --- /dev/null +++ b/structural_patterns/decorator.rst @@ -0,0 +1,176 @@ +.. _decorator: + +装饰模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +一般有两种方式可以实现给一个类或对象增加行为: +? 继承机制,使用继承机制是给现有类添加功能的一种有效途径, +通过继承一个现有类可以使得子类在拥有自身方法的同时还拥 +有父类的方法。但是这种方法是静态的,用户不能控制增加行 +为的方式和时机。 +? 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对 +象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们 +称这个嵌入的对象为装饰器(Decorator) + +装饰模式以对客户透明的方式动态地给一个对象附加上 +更多的责任,换言之,客户端并不会觉得对象在装饰前 +和装饰后有什么不同。装饰模式可以在不需要创造更多 +子类的情况下,将对象的功能加以扩展。这就是装饰模 +式的模式动机。 + + +模式定义 +-------------------- +装饰模式(Decorator Pattern) :动态地给一个对象增 +加一些额外的职责(Responsibility),就增加对象功能 +来说,装饰模式比生成子类实现更为灵活。其别名也可 +以称为包装器(Wrapper),与适配器模式的别名相同, +但它们适用于不同的场合。根据翻译的不同,装饰模式 +也有人称之为“油漆工模式”,它是一种对象结构型模 +式。 + + +模式结构 +-------------------- +装饰模式包含如下角色: +? Component: 抽象构件 +? ConcreteComponent: 具体构件 +? Decorator: 抽象装饰类 +? ConcreteDecorator: 具体装饰类 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + + +模式分析 +-------------------- +- 与继承关系相比,关联关系的主要优势在于不会破坏类的封装 +性,而且继承是一种耦合度较大的静态关系,无法在程序运行 +时动态扩展。在软件开发阶段,关联关系虽然不会比继承关系 +减少编码量,但是到了软件维护阶段,由于关联关系使系统具 +有较好的松耦合性,因此使得系统更加容易维护。当然,关联 +关系的缺点是比继承关系要创建更多的对象。 +- 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的 +方式动态地给一个对象附加更多的责任。装饰模式可以在不需 +要创造更多子类的情况下,将对象的功能加以扩展。 + + +实例 +-------------------- +实例:变形金刚 + +变形金刚在变形之前是一辆汽车,它 +可以在陆地上移动。当它变成机器人 +之后除了能够在陆地上移动之外,还 +可以说话;如果需要,它还可以变成 +飞机,除了在陆地上移动还可以在天 +空中飞翔。 + + +优点 +-------------------- +装饰模式的优点 +? 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可 +以提供比继承更多的灵活性。 +? 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以 +在运行时选择不同的装饰器,从而实现不同的行为。 +? 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出 +很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得 +到功能更为强大的对象。 +? 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的 +具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须 +改变,符合“开闭原则” + + +缺点 +-------------------- +装饰模式的缺点 +? 使用装饰模式进行系统设计时将产生很多小对象,这些对象的 +区别在于它们之间相互连接的方式有所不同,而不是它们的类 +或者属性值有所不同,同时还将产生很多具体装饰类。这些装 +饰类和小对象的产生将增加系统的复杂度,加大学习与理解的 +难度。 +? 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继 +承更加易于出错,排错也很困难,对于多次装饰的对象,调试 +时寻找错误可能需要逐级排查,较为烦琐。 + + +适用环境 +-------------------- +在以下情况下可以使用装饰模式: +? 在不影响其他对象的情况下,以动态、透明的方式给单个对象 +添加职责。 +? 需要动态地给一个对象增加功能,这些功能也可以动态地被撤 +销。 +? 当不能采用继承的方式对系统进行扩充或者采用继承不利于系 +统扩展和维护时。不能采用继承的情况主要有两类:第一类是 +系统中存在大量独立的扩展,为支持每一种组合将产生大量的 +子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能 +继承(如final类) + + +模式应用 +-------------------- + +模式扩展 +-------------------- +装饰模式的简化-需要注意的问题 +? 一个装饰类的接口必须与被装饰类的接口保持相同,对于客户 +端来说无论是装饰之前的对象还是装饰之后的对象都可以一致 +对待。 +? 尽量保持具体构件类Component作为一个“轻”类,也就是说 +不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类 +对其进行扩展。 +? 如果只有一个具体构件类而没有抽象构件类,那么抽象装饰类 +可以作为具体构件类的直接子类。 + + +总结 +-------------------- +- 装饰模式用于动态地给一个对象增加一些额外的职责,就增加对象功 +能来说,装饰模式比生成子类实现更为灵活。它是一种对象结构型模 +式。 +- 装饰模式包含四个角色:抽象构件定义了对象的接口,可以给这些对 +象动态增加职责(方法);具体构件定义了具体的构件对象,实现了 +在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法); +抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但是具 +体职责在其子类中实现;具体装饰类是抽象装饰类的子类,负责向构 +件添加新的职责。 +- 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动 +态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子 +类的情况下,将对象的功能加以扩展。 +- 装饰模式的主要优点在于可以提供比继承更多的灵活性,可以通过一种动态的 +方式来扩展一个对象的功能,并通过使用不同的具体装饰类以及这些装饰类的 +排列组合,可以创造出很多不同行为的组合,而且具体构件类与具体装饰类可 +以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类;其主要缺 +点在于使用装饰模式进行系统设计时将产生很多小对象,而且装饰模式比继承 +更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需 +要逐级排查,较为烦琐。 +- 装饰模式适用情况包括:在不影响其他对象的情况下,以动态、透明的方式给 +单个对象添加职责;需要动态地给一个对象增加功能,这些功能也可以动态地 +被撤销;当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展 +和维护时。 +- 装饰模式可分为透明装饰模式和半透明装饰模式:在透明装饰模式中,要求客 +户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构 +件类型和具体装饰类型,而应该全部声明为抽象构件类型;半透明装饰模式允 +许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法。 + + diff --git a/structural_patterns/facade.rst b/structural_patterns/facade.rst new file mode 100644 index 0000000..700787d --- /dev/null +++ b/structural_patterns/facade.rst @@ -0,0 +1,166 @@ +.. _facade: + +外观模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- + +模式定义 +-------------------- +外观模式(Facade Pattern):外部与一个子系统的通 +信必须通过一个统一的外观对象进行,为子系统中的一 +组接口提供一个一致的界面,外观模式定义了一个高层 +接口,这个接口使得这一子系统更加容易使用。外观模 +式又称为门面模式,它是一种对象结构型模式。 + + +模式结构 +-------------------- +外观模式包含如下角色: +? Facade: 外观角色 +? SubSystem:子系统角色 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + + +模式分析 +-------------------- +根据“单一职责原则”,在软件中将一个系统划分为若干个子 +系统有利于降低整个系统的复杂性,一个常见的设计目标是使 +子系统间的通信和相互依赖关系达到最小,而达到该目标的途 +径之一就是引入一个外观对象,它为子系统的访问提供了一个 +简单而单一的入口。 +?外观模式也是“迪米特法则”的体现,通过引入一个新的外观 +类可以降低原有系统的复杂度,同时降低客户类与子系统类的 +耦合度。 +- 外观模式要求一个子系统的外部与其内部的通信通过一个统一的 +外观对象进行,外观类将客户端与子系统的内部复杂性分隔开, +使得客户端只需要与外观对象打交道,而不需要与子系统内部的 +很多对象打交道。 +?外观模式的目的在于降低系统的复杂程度。 +?外观模式从很大程度上提高了客户端使用的便捷性,使得客户端 +无须关心子系统的工作细节,通过外观角色即可调用相关功能。 + +实例 +-------------------- + +优点 +-------------------- +外观模式的优点 +? 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使 +用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之 +关联的对象也很少。 +? 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不 +会影响到调用它的客户类,只需要调整外观类即可。 +? 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间 +的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。 +一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化 +也不会影响到外观对象。 +? 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系 +统类。 + + +缺点 +-------------------- +外观模式的缺点 +? 不能很好地限制客户使用子系统类,如果对客户访问子系统类 +做太多的限制则减少了可变性和灵活性。 +? 在不引入抽象外观类的情况下,增加新的子系统可能需要修改 +外观类或客户端的源代码,违背了“开闭原则”。 + + +适用环境 +-------------------- +在以下情况下可以使用外观模式: +? 当要为一个复杂子系统提供一个简单接口时可以使用外观模式。 +该接口可以满足大多数用户的需求,而且用户也可以越过外观 +类直接访问子系统。 +? 客户程序与多个子系统之间存在很大的依赖性。引入外观类将 +子系统与客户以及其他子系统解耦,可以提高子系统的独立性 +和可移植性。 +? 在层次化结构中,可以使用外观模式定义系统中每一层的入口, +层与层之间不直接产生联系,而通过外观类建立联系,降低层 +之间的耦合度。 + + +模式应用 +-------------------- + +模式扩展 +-------------------- +一个系统有多个外观类 +? 在外观模式中,通常只需要一个外观类,并且此外观类只有一 +个实例,换言之它是一个单例类。在很多情况下为了节约系统 +资源,一般将外观类设计为单例类。当然这并不意味着在整个 +系统里只能有一个外观类,在一个系统中可以设计多个外观类, +每个外观类都负责和一些特定的子系统交互,向用户提供相应 +的业务功能。 + +不要试图通过外观类为子系统增加新行为 +? 不要通过继承一个外观类在子系统中加入新的行为,这种做法 +是错误的。外观模式的用意是为子系统提供一个集中化和简化 +的沟通渠道,而不是向子系统加入新的行为,新的行为的增加 +应该通过修改原有子系统类或增加新的子系统类来实现,不能 +通过外观类来实现。 + +外观模式与迪米特法则 +? 外观模式创造出一个外观对象,将客户端所涉及的属于一个子 +系统的协作伙伴的数量减到最少,使得客户端与子系统内部的 +对象的相互作用被外观对象所取代。外观类充当了客户类与子 +系统类之间的“第三者”,降低了客户类与子系统类之间的耦 +合度,外观模式就是实现代码重构以便达到“迪米特法则”要 +求的一个强有力的武器。 + +抽象外观类的引入 +? 外观模式最大的缺点在于违背了“开闭原则”,当增加新的子 +系统或者移除子系统时需要修改外观类,可以通过引入抽象外 +观类在一定程度上解决该问题,客户端针对抽象外观类进行编 +程。对于新的业务需求,不修改原有外观类,而对应增加一个 +新的具体外观类,由新的具体外观类来关联新的子系统对象, +同时通过修改配置文件来达到不修改源代码并更换外观类的目 +的。 + + +总结 +-------------------- +在外观模式中,外部与一个子系统的通信必须通过一个统一的外观对 +象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义 +了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式 +又称为门面模式,它是一种对象结构型模式。 +? 外观模式包含两个角色:外观角色是在客户端直接调用的角色,在外 +观角色中可以知道相关的(一个或者多个)子系统的功能和责任,它将所 +有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统 +对象处理;在软件系统中可以同时有一个或者多个子系统角色,每一 +个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统 +的功能。 +? 外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观 +对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户 +端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交 +道。 +外观模式主要优点在于对客户屏蔽子系统组件,减少了客户处理的对 +象数目并使得子系统使用起来更加容易,它实现了子系统与客户之间 +的松耦合关系,并降低了大型软件系统中的编译依赖性,简化了系统 +在不同平台之间的移植过程;其缺点在于不能很好地限制客户使用子 +系统类,而且在不引入抽象外观类的情况下,增加新的子系统可能需 +要修改外观类或客户端的源代码,违背了“开闭原则”。 +? 外观模式适用情况包括:要为一个复杂子系统提供一个简单接口;客 +户程序与多个子系统之间存在很大的依赖性;在层次化结构中,需要 +定义系统中每一层的入口,使得层与层之间不直接产生联系。 diff --git a/structural_patterns/flyweight.rst b/structural_patterns/flyweight.rst new file mode 100644 index 0000000..1578890 --- /dev/null +++ b/structural_patterns/flyweight.rst @@ -0,0 +1,176 @@ +.. _flyweight: + +享元模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +面向对象技术可以很好地解决一些灵活性或可扩展性问 +题,但在很多情况下需要在系统中增加类和对象的个数。 +当对象数量太多时,将导致运行代价过高,带来性能下 +降等问题。 +?享元模式正是为解决这一类问题而诞生的。享元模式通 +过共享技术实现相同或相似对象的重用。 +- 在享元模式中可以共享的相同内容称为内部状态(Intrinsic +State),而那些需要外部环境来设置的不能共享的内容称为 +外部状态(Extrinsic State),由于区分了内部状态和外部状 +态,因此可以通过设置不同的外部状态使得相同的对象可以 +具有一些不同的特征,而相同的内部状态是可以共享的。 +- 在享元模式中通常会出现工厂模式,需要创建一个享元工厂 +来负责维护一个享元池(Flyweight Pool)用于存储具有相同 +内部状态的享元对象。 +- 在享元模式中共享的是享元对象的内部状态,外部状态 +需要通过环境来设置。在实际使用中,能够共享的内部 +状态是有限的,因此享元对象一般都设计为较小的对象, +它所包含的内部状态较少,这种对象也称为细粒度对象。 +享元模式的目的就是使用共享技术来实现大量细粒度对 +象的复用 + + +模式定义 +-------------------- +享元模式(Flyweight Pattern):运用共享技术有效地 +支持大量细粒度对象的复用。系统只使用少量的对象, +而这些对象都很相似,状态变化很小,可以实现对象的 +多次复用。由于享元模式要求能够共享的对象必须是细 +粒度对象,因此它又称为轻量级模式,它是一种对象结 +构型模式。 + + +模式结构 +-------------------- +享元模式包含如下角色: +? Flyweight: 抽象享元类 +? ConcreteFlyweight: 具体享元类 +? UnsharedConcreteFlyweight: 非共享具体享元类 +? FlyweightFactory: 享元工厂类 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + + +模式分析 +-------------------- +享元模式是一个考虑系统性能的设计模式,通过使用 +享元模式可以节约内存空间,提高系统的性能。 + +享元模式的核心在于享元工厂类,享元工厂类的作用 +在于提供一个用于存储享元对象的享元池,用户需要 +对象时,首先从享元池中获取,如果享元池中不存在, +则创建一个新的享元对象返回给用户,并在享元池中 +保存该新增对象。 + +享元模式以共享的方式高效地支持大量的细粒度对象,享元对象 +能做到共享的关键是区分内部状态(Internal State)和外部状态 +(External State)。 +?(1) 内部状态是存储在享元对象内部并且不会随环境改变而改 +变的状态,因此内部状态可以共享。 +?(2) 外部状态是随环境改变而改变的、不可以共享的状态。享 +元对象的外部状态必须由客户端保存,并在享元对象被创建之 +后,在需要使用的时候再传入到享元对象内部。一个外部状态 +与另一个外部状态之间是相互独立的。 + + + +实例 +-------------------- + +优点 +-------------------- +享元模式的优点 +? 享元模式的优点在于它可以极大减少内存中对象的数 +量,使得相同对象或相似对象在内存中只保存一份。 +? 享元模式的外部状态相对独立,而且不会影响其内部 +状态,从而使得享元对象可以在不同的环境中被共享。 + + +缺点 +-------------------- +享元模式的缺点 +? 享元模式使得系统更加复杂,需要分离出内部状态和 +外部状态,这使得程序的逻辑复杂化。 +? 为了使对象可以共享,享元模式需要将享元对象的状 +态外部化,而读取外部状态使得运行时间变长。 + + +适用环境 +-------------------- +在以下情况下可以使用享元模式: +? 一个系统有大量相同或者相似的对象,由于这类对象 +的大量使用,造成内存的大量耗费。 +? 对象的大部分状态都可以外部化,可以将这些外部状 +态传入对象中。 +? 使用享元模式需要维护一个存储享元对象的享元池, +而这需要耗费资源,因此,应当在多次重复使用享元 +对象时才值得使用享元模式。 + + +模式应用 +-------------------- +享元模式在编辑器软件中大量使用,如在一个文 +档中多次出现相同的图片,则只需要创建一个图片对 +象,通过在应用程序中设置该图片出现的位置,可以 +实现该图片在不同地方多次重复显示。 + + + +模式扩展 +-------------------- +单纯享元模式和复合享元模式 + +- 单纯享元模式:在单纯享元模式中,所有的享元对象都是可以 +共享的,即所有抽象享元类的子类都可共享,不存在非共享具 +体享元类 +- 复合享元模式:将一些单纯享元使用组合模式加以组合,可以 +形成复合享元对象,这样的复合享元对象本身不能共享,但是 +它们可以分解成单纯享元对象,而后者则可以共享。 + +享元模式与其他模式的联用 +? 在享元模式的享元工厂类中通常提供一个静态的工厂方法 +用于返回享元对象,使用简单工厂模式来生成享元对象。 +? 在一个系统中,通常只有唯一一个享元工厂,因此享元工 +厂类可以使用单例模式进行设计。 +? 享元模式可以结合组合模式形成复合享元模式,统一对享 +元对象设置外部状态。 + + +总结 +-------------------- +享元模式运用共享技术有效地支持大量细粒度对象的复用。系统 +只使用少量的对象,而这些对象都很相似,状态变化很小,可以 +实现对象的多次复用,它是一种对象结构型模式。 +? 享元模式包含四个角色:抽象享元类声明一个接口,通过它可以 +接受并作用于外部状态;具体享元类实现了抽象享元接口,其实 +例称为享元对象;非共享具体享元是不能被共享的抽象享元类的 +子类;享元工厂类用于创建并管理享元对象,它针对抽象享元类 +编程,将各种类型的具体享元对象存储在一个享元池中。 +? 享元模式以共享的方式高效地支持大量的细粒度对象,享元对象 +能做到共享的关键是区分内部状态和外部状态。其中内部状态是 +存储在享元对象内部并且不会随环境改变而改变的状态,因此内 +部状态可以共享;外部状态是随环境改变而改变的、不可以共享 +的状态。 +?享元模式主要优点在于它可以极大减少内存中对象的数量, +使得相同对象或相似对象在内存中只保存一份;其缺点是 +使得系统更加复杂,并且需要将享元对象的状态外部化, +而读取外部状态使得运行时间变长。 +?享元模式适用情况包括:一个系统有大量相同或者相似的 +对象,由于这类对象的大量使用,造成内存的大量耗费; +对象的大部分状态都可以外部化,可以将这些外部状态传 +入对象中;多次重复使用享元对象。 + diff --git a/structural_patterns/proxy.rst b/structural_patterns/proxy.rst new file mode 100644 index 0000000..4028a62 --- /dev/null +++ b/structural_patterns/proxy.rst @@ -0,0 +1,178 @@ +.. _proxy: + +代理模式 +==================== + +.. contents:: 目录 + +模式动机 +-------------------- +在某些情况下,一个客户不想或者不能直接引用一个对 +象,此时可以通过一个称之为“代理”的第三者来实现 +间接引用。代理对象可以在客户端和目标对象之间起到 +中介的作用,并且可以通过代理对象去掉客户不能看到 +的内容和服务或者添加客户需要的额外服务。 + +通过引入一个新的对象(如小图片和远程代理 +对象)来实现对真实对象的操作或者将新的对 +象作为真实对象的一个替身,这种实现机制即 +为代理模式,通过引入代理对象来间接访问一 +个对象,这就是代理模式的模式动机。 + + +模式定义 +-------------------- +代理模式(Proxy Pattern) :给某一个对象提供一个代 +理,并由代理对象控制对原对象的引用。代理模式的英 +文叫做Proxy或Surrogate,它是一种对象结构型模式。 + + +模式结构 +-------------------- +代理模式包含如下角色: +? Subject: 抽象主题角色 +? Proxy: 代理主题角色 +? RealSubject: 真实主题角色 + + +.. image:: /_static/SimpleFactory.jpg + + +时序图 +-------------------- +.. image:: /_static/seq_SimpleFactory.jpg + +代码分析 +-------------------- +.. literalinclude:: /code/SimpleFactory/Factory.cpp + :language: cpp + :linenos: + :lines: 1-10,24- + :emphasize-lines: 12-19 + + + +模式分析 +-------------------- + +实例 +-------------------- + +优点 +-------------------- +代理模式的优点 +? 代理模式能够协调调用者和被调用者,在一定程度上降低了系 +统的耦合度。 +? 远程代理使得客户端可以访问在远程机器上的对象,远程机器 +可能具有更好的计算性能与处理速度,可以快速响应并处理客 +户端请求。 +? 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 +统资源的消耗,对系统进行优化并提高运行速度。 +? 保护代理可以控制对真实对象的使用权限 + + + +缺点 +-------------------- +代理模式的缺点 +? 由于在客户端和真实主题之间增加了代理对象,因此 +有些类型的代理模式可能会造成请求的处理速度变慢。 +? 实现代理模式需要额外的工作,有些代理模式的实现 +非常复杂。 + + +适用环境 +-------------------- +根据代理模式的使用目的,常见的代理模式有以下几种类型: +? 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地 +的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在 +另一台主机中,远程代理又叫做大使(Ambassador)。 +? 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一 +个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。 +? Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟 +到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个 +开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象 +被用到的时候才被克隆。 +- 保护(Protect or Access)代理:控制对一个对象的访问,可以给 +不同的用户提供不同级别的使用权限。 +? 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空 +间,以便多个客户端可以共享这些结果。 +? 防火墙(Firewall)代理:保护目标不让恶意用户接近。 +? 同步化(Synchronization)代理:使几个用户能够同时使用一个对 +象而没有冲突。 +? 智能引用(Smart Reference)代理:当一个对象被引用时,提供 +一些额外的操作,如将此对象被调用的次数记录下来等。 + + +模式应用 +-------------------- +EJB、Web Service等分布式技术都是代理模式 +的应用。在EJB中使用了RMI机制,远程服务器中的 +企业级Bean在本地有一个桩代理,客户端通过桩来调 +用远程对象中定义的方法,而无须直接与远程对象交 +互。在EJB的使用中需要提供一个公共的接口,客户 +端针对该接口进行编程,无须知道桩以及远程EJB的 +实现细节 + + +模式扩展 +-------------------- +几种常用的代理模式 +? 图片代理:一个很常见的代理模式的应用实例就是对大图浏览的控制。 +? 用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象 +的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端 +浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的 +加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片 +在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以 +再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编 +程将真实图片的加载放到后台来操作,不影响前台图片的浏览。 +- 远程代理:远程代理可以将网络的细节隐藏起来,使得客户端 +不必考虑网络的存在。客户完全可以认为被代理的远程业务对 +象是局域的而不是远程的,而远程代理对象承担了大部分的网 +络通信工作。 +- 虚拟代理:当一个对象的加载十分耗费资源的时候,虚拟代理 +的优势就非常明显地体现出来了。虚拟代理模式是一种内存节 +省技术,那些占用大量内存或处理复杂的对象将推迟到使用它 +的时候才创建。 +?在应用程序启动的时候,可以用代理对象代替真实对象初始化, +节省了内存的占用,并大大加速了系统的启动时间。 + +动态代理 +? 动态代理是一种较为高级的代理模式,它的典型应用就是Spring AOP。 +? 在传统的代理模式中,客户端通过Proxy调用RealSubject类的request() +方法,同时还在代理类中封装了其他方法(如preRequest()和 +postRequest()),可以处理一些其他问题。 +? 如果按照这种方法使用代理模式,那么真实主题角色必须是事先已经 +存在的,并将其作为代理对象的内部成员属性。如果一个真实主题角 +色必须对应一个代理主题角色,这将导致系统中的类个数急剧增加, +因此需要想办法减少系统中类的个数,此外,如何在事先不知道真实 +主题角色的情况下使用代理主题角色,这都是动态代理需要解决的问 +题。 + + + +总结 +-------------------- +在代理模式中,要求给某一个对象提供一个代理,并由代理对象控制 +对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对 +象结构型模式。 +? 代理模式包含三个角色:抽象主题角色声明了真实主题和代理主题的 +共同接口;代理主题角色内部包含对真实主题的引用,从而可以在任 +何时候操作真实主题对象;真实主题角色定义了代理角色所代表的真 +实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过 +代理主题角色间接调用真实主题角色中定义的方法。 +? 代理模式的优点在于能够协调调用者和被调用者,在一定程度上降低 +了系统的耦合度;其缺点在于由于在客户端和真实主题之间增加了代 +理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢, +并且实现代理模式需要额外的工作,有些代理模式的实现非常复杂。 +远程代理为一个位于不同的地址空间的对象提供一个本地的代表对象, +它使得客户端可以访问在远程机器上的对象,远程机器可能具有更好 +的计算性能与处理速度,可以快速响应并处理客户端请求。 +? 如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的 +对象来表示,真实对象只在需要时才会被真正创建,这个小对象称为 +虚拟代理。虚拟代理通过使用一个小对象来代表一个大对象,可以减 +少系统资源的消耗,对系统进行优化并提高运行速度。 +? 保护代理可以控制对一个对象的访问,可以给不同的用户提供不同级 +别的使用权限。 + +