面向对象方法与技术-设计模式实践_第1页
面向对象方法与技术-设计模式实践_第2页
面向对象方法与技术-设计模式实践_第3页
面向对象方法与技术-设计模式实践_第4页
面向对象方法与技术-设计模式实践_第5页
已阅读5页,还剩173页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

面向对象的技术与方法共178页第2页visitor(访问者)模式问题:在面向对象系统的开发和设计过程,经常会遇到一种情况就是需求变更(RequirementChanging),经常我们做好的一个设计、实现了一个系统原型,咱们的客户又会有了新的需求。我们又因此不得不去修改已有的设计,最常见就是解决方案就是给已经设计、实现好的类添加新的方法去实现客户新的需求,这样就陷入了设计变更的梦魇:不停地打补丁,其带来的后果就是设计根本就不可能封闭、编译永远都是整个系统代码。共178页第3页visitor(访问者)模式意图:表示一个作用于某对象结构中的各元素的操作,将更新封装到一个类中(访问操作),并由待更改类提供一个接收接口。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。共178页第4页动机:

考虑一个编译器,它将源程序表示为一个抽象语法树。该编译器需在抽象语法树上实施某些操作以进行“静态语义”分析,例如检查是否所有的变量都已经被定义了。它也需要生成代码。因此它可能要定义许多操作以进行类型检查、代码优化、流程分析,检查变量是否在使用前被赋初值,等等。visitor(访问者)模式共178页第5页visitor(访问者)模式

这些操作大多要求对不同的节点进行不同的处理。例如对代表赋值语句的结点的处理就不同于对代表变量或算术表达式的结点的处理。因此有用于赋值语句的类,有用于变量访问的类,还有用于算术表达式的类,等等。结点类的集合当然依赖于被编译的语言,但对于一个给定的语言其变化不大。共178页第6页visitor(访问者)模式共178页第7页visitor(访问者)模式存在的问题:将所有这些操作分散到各种结点类中会导致整个系统难以理解、难以维护和修改。此外,增加新的操作通常需要重新编译所有这些类。如果可以独立地增加新的操作,并且使这些结点类独立于作用于其上的操作,将会更好一些。共178页第8页visitor(访问者)模式

实现上述两个目标,可以将每一个类中相关的操作包装在一个独立的对象(称为一个Visitor)中,并在遍历抽象语法树时将此对象传递给当前访问的元素。当一个元素“接受”该访问者时,该元素向访问者发送一个包含自身类信息的请求。该请求同时也将该元素本身作为一个参数。然后访问者将为该元素执行该操作—这一操作以前是在该元素的类中的。共178页第9页visitor(访问者)模式使用Visitor模式的编译器:

若要对一个过程进行类型检查,那么它将会创建一个TypeCheckingVisitor对象,并以这个对象为一个参数在抽象语法树上调用Accept操作。每一个结点在实现Accept时将会回调访问者:一个赋值结点调用访问者的VisitAssignment操作,而一个变量引用将调用VisitVariableReference。以前类AssignmentNode的TypeCheck操作现在成为TypeCheckingVisitor的VisitAssignment操作。共178页第10页visitor(访问者)模式共178页第11页visitor(访问者)模式共178页第12页visitor(访问者)模式

visitor模式将每一个编译步骤的操作封装在一个与该步骤相关的visitor中。使用visitor模式,必须定义两个类层次:一个对应于接受操作的元素(Node层次)另一个对应于定义对元素的操作的访问者(NodeVisitor层次)。给访问者类层次增加一个新的子类即可创建一个新的操作。只要该编译器接受的语法不改变即不需要增加新的Node子类),我们就可以简单的定义新的NodeVisitor子类以增加新的功能。共178页第13页适用性:

一个对象结构包含许多对象类,我们想执行一些依赖于其具体类的操作。

要对一个对象结构中的对象进行很多不同的并且不相关的操作,又不想改变这些对象类。定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类,需要重新定义所有visitor的接口。visitor(访问者)模式共178页第14页

结构共178页第15页参与者:Visitor(访问者,如NodeVisitor)—

为该对象结构中ConcreteElement的每一个类声明一个Visit操作。ConcreteVisitor(具体访问者,如:

TypeCheckingVisitor)—

实现每个由Visitor声明的操作。Element(元素,如Node)—

定义一个Accept操作,它以一个访问者为参数。visitor(访问者)模式共178页第16页ConcreteElement(具体元素,如:

AssignmentNode,VariableRefNode)—

实现Accept操作,该操作以一个访问者为参数。ObjectStructure(对象结构,如Program)—

能枚举它的元素。—

可以提供一个高层的接口以允许该访问者访问它的元素。—

可以是一个复合(或是一个集合,如一个列表或一个无序集合。visitor(访问者)模式共178页第17页协作:

一个使用Visitor模式的客户必须创建一个ConcreteVisitor对象,然后遍历该对象结构,并用该访问者访问每一个元素。当一个元素被访问时,它调用对应于它的类的Visitor操作。如果必要,该元素将自身作为这个操作的一个参数以便该访问者访问它的状态。visitor(访问者)模式共178页第18页下面的交互框图说明了一个对象结构、一个访问者和两个元素之间的协作。共178页第19页效果1)访问者模式使得易于增加新的操作2)访问者集中相关的操作而分离无关的操作3)增加新的ConcreteElement类很困难4)通过类层次进行访问5)破坏封装visitor(访问者)模式共178页第20页实现在C++中,Visitor类可以这样定义:共178页第21页每个ConcreteElement类实现一个Accept操作,这个操作调用访问者中相应于本ConcreteElement类的Visit...的操作。这样最终得到调用的操作不仅依赖于该元素的类也依赖于访问者的类。具体元素声明为:共178页第22页visitor(访问者)模式共178页第23页一个CompositeElement类可能象这样实现Accept:共178页第24页代码示例因为访问者通常与复合相关,我们将使用在Composite代码示例一节中定义的Equipment类来说明Visitor模式。共178页第25页所有设备访问者的抽象父类对每一个设备子类都有一个虚函数。所有的虚函数的缺省行为都是什么也不做。共178页第26页Equipment子类以基本相同的方式定义Accept:调用EquipmentVisitor中的对应于接受Accept请求的类的操作,如:共178页第27页包含其他设备的设备实现Accept时,遍历其各个子构件并调用它们各自的Accept操作,然后对自己调用Visit操作。例如,

Chassis::Accept可象如下这样遍历底盘中的所有部件:共178页第28页PricingVisitor计算该设备结构的价格。它计算所有的简单设备(如软盘)的实价以及所有复合设备(如底盘和公共汽车)打折后的价格。共178页第29页visitor(访问者)模式共178页第30页我们可以象这样定义一个计算存货清单的类:共178页第31页InventoryVisitor为对象结构中的每一种类型的设备累计总和。共178页第32页下面是如何在一个设备结构上使用

InventoryVisitor:共178页第33页visitor模式

使用访问者模式时对象群结构(Collection)中的对象类型很少改变。若对象群中的对象类型经常改变,建议在这些对象类中逐个定义操作。共178页第34页TEMPLATEMETHOD模式问题:

在面向对象系统的分析与设计过程中经常会遇到这样一种情况:对于某一个业务逻辑(算法实现)在不同的对象中有不同的细节实现,但是逻辑(算法)的框架(或通用的应用算法)是相同的。共178页第35页意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。TEMPLATEMETHOD模式共178页第36页适用性:

一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。控制子类扩展。TEMPLATEMETHOD模式共178页第37页结构共178页第38页AbstractClass(抽象类,如Application)

—

定义抽象的原语操作(primitiveoperation),具体的子类将重定义它们以实现一个算法的各步骤。

—

实现一个模板方法,定义一个算法的骨架。ConcreteClass(具体类,如MyApplication)

—

实现原语操作以完成算法中与特定子类相关的步骤。TEMPLATEMETHOD模式共178页第39页协作

ConcreteClass靠AbstractClass来实现算法中不变的步骤。TEMPLATEMETHOD模式共178页第40页

代码示例:

下面的C++实例说明了一个父类如何强制其子类遵循一种不变的结构。考虑一个支持在屏幕上绘图的类View。一个视图在进入“焦点”(focus)状态时才可设定合适的特定绘图状态(如颜色和字体),因而只有成为“焦点”之后才能进行绘图。View类强制其子类遵循这个规则。TEMPLATEMETHOD模式共178页第41页我们用Display模板方法来解决这个问题。TEMPLATEMETHOD模式共178页第42页为维持不变部分,View的客户通常调用Display,而View的子类通常重定义DoDisplay。View本身的DoDisplay什么也不做:子类重定义它以增加它们的特定绘图行为:共178页第43页TEMPLATEMETHOD模式讨论:

Template模式实际上就是利用面向对象中多态的概念实现算法实现细节和高层接口的松耦合。

Template

是采用继承的方式实现算法的异构,其关键点就是将通用算法封装在抽象基类中,并将不同的算法细节放到子类中实现。共178页第44页TEMPLATEMETHOD模式Template模式获得一种反向控制结构效果,这也是面向对象系统的分析和设计中一个原则DIP(依赖倒置:DependencyInversionPrinciples)。其含义就是父类调用子类的操作(高层模块调用低层模块的操作),低层模块实现高层模块声明的接口。这样控制权在父类(高层模块),低层模块反而要依赖高层模块。共178页第45页TEMPLATEMETHOD模式

继承的强制性约束关系也让Template模式有不足的地方,原语方法Primitive(),是不能被别的类复用。共178页第46页TEMPLATEMETHOD模式Strategy模式VsTemplate模式实现一个抽象接口的两种方式:继承和组合之间的区别继承方式:将抽象接口声明在基类中,将具体的实现放在具体子类中。组合方式:将接口的实现放在被组合对象中,将抽象接口放在组合类中。共178页第47页TEMPLATEMETHOD模式继承优点:

1)易于修改和扩展那些被复用的实现。缺点:

1)破坏了封装性,继承中父类的实现细节暴露给子类了;

2)“白盒”复用,原因在1)中;

3)当父类的实现更改时,其所有子类将不得不随之改变

4)从父类继承而来的实现在运行期间不能改变(编译期间就已经确定了)共178页第48页TEMPLATEMETHOD模式组合优点:1)“黑盒”复用,因为被包含对象的内部细节对外是不可见的;2)封装性好,原因为1);3)实现和抽象的依赖性很小(组合对象和被组合对象之间的依赖性小);4)可以在运行期间动态定义实现(通过一个指向相同类型的指针,典型的是抽象基类的指针)。缺点:1)系统中对象过多。共178页第49页TEMPLATEMETHOD模式在面向对象的设计中的有一条很重要的原则就是:

优先使用(对象)组合,而非(类)继承(FavorCompositionOverInheritance)。共178页第50页ChainofResponsibility模式问题:

熟悉VC/MFC的都知道,VC是“基于消息,事件驱动”,消息在VC开发中起着举足轻重的作用。在MFC中,消息是通过一个向上递交的方式进行处理,例如一个WM_COMMAND消息的处理流程为:

1)

MDI主窗口(CMDIFrameWnd)收到命令消息WM_COMMAND,其ID位ID_×××;

2)

MDI主窗口将消息传给当前活动的MDI子窗口(CMDIChildWnd);共178页第51页ChainofResponsibility模式3)

MDI子窗口给自己的子窗口(View)一个处理机会,将消息交给View;4)

View检查自己MessageMap;5)如果View没有发现处理该消息的程序,则将该消息传给其对应的Document对象;否则View处理,消息流程结束;6)

Document检查自己MessageMap,如果没有该消息的处理程序,则将该消息传给其对象的DocumentTemplate处理;否则自己处理,消息流程结束;共178页第52页ChainofResponsibility模式7)如果在6)中消息没有得到处理,则将消息返回给View;8)

View再传回给MDI子窗口;9)

MDI子窗口将该消息传给CwinApp对象,CwinApp为所有无主的消息提供了处理。共178页第53页ChainofResponsibility模式意图:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。共178页第54页ChainofResponsibility模式举例:考虑一个图形用户界面中的上下文有关的帮助机制。用户在界面的任一部分上点击就可以得到帮助信息,所提供的帮助依赖于点击的是界面的哪一部分以及其上下文。例如,对话框中的按钮的帮助信息就可能和主窗口中类似的按钮不同。如果对那一部分界面没有特定的帮助信息,那么帮助系统应该显示一个关于当前上下文的较一般的帮助信息—比如说,整个对话框。共178页第55页ChainofResponsibility模式

因此很自然地,应根据普遍性(generality)即从最特殊到最普遍的顺序来组织帮助信息。而且,很明显,在这些用户界面对象中会有一个对象来处理帮助请求;至于是哪一个对象则取决于上下文以及可用的帮助具体到何种程度。这儿的问题是提交帮助请求的对象(如按钮)并不明确知道谁是最终提供帮助的对象。我们要有一种办法将提交帮助请求的对象与可能提供帮助信息的对象解耦(decouple)。共178页第56页ChainofResponsibility模式共178页第57页ChainofResponsibility模式帮助请求传递链共178页第58页ChainofResponsibility模式共178页第59页ChainofResponsibility模式适用性:有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。可处理一个请求的对象集合应被动态指定。共178页第60页ChainofResponsibility模式结构共178页第61页ChainofResponsibility模式典型的对象结构共178页第62页ChainofResponsibility模式参与者Handler(如HelpHandler)

—

定义一个处理请求的接口。

—

(可选)实现后继链。

ConcreteHandler(如PrintButton和PrintDialog)

—

处理它所负责的请求。

—

可访问它的后继者。

—

如果可处理该请求,就处理之;否则将该请求转发给它的后继者。

Client

—

向链上的具体处理者(ConcreteHandler)对象提交请求。共178页第63页ChainofResponsibility模式协作当客户提交一个请求时,请求沿链传递直至有一个ConcreteHandler对象负责处理它。共178页第64页ChainofResponsibility模式效果1)降低耦合度2)增强了给对象指派职责(Responsibility)的灵活性3)不保证被接受共178页第65页ChainofResponsibility模式代码示例描述了在线帮助系统中,职责链是如何处理请求的

HelpHandler类定义了处理帮助请求的接口。它维护一个帮助主题(缺省值为空),并保持对帮助处理对象链中它的后继者的引用。关键的操作是HandleHelp,它可被子类重定义。HasHelp是一个辅助操作,用于检查是否有一个相关的帮助主题。共178页第66页ChainofResponsibility模式共178页第67页ChainofResponsibility模式共178页第68页ChainofResponsibility模式

所有的窗口组件都是Widget抽象类的子类。Widget是HelpHandler的子类,因为所有的用户界面元素都可有相关的帮助。共178页第69页ChainofResponsibility模式在本例中,按钮是链上的第一个处理者。Button类是Widget类的子类。Button构造函数有两个参数:对包含它的窗口组件的引用和其自身的帮助主题。共178页第70页ChainofResponsibility模式共178页第71页ChainofResponsibility模式Dialog实现了一个类似的策略,只不过它的后继者不是一个窗口组件而是任意的帮助请求处理对象。在本例中这个后继者将是Application的一个实例。共178页第72页ChainofResponsibility模式共178页第73页ChainofResponsibility模式共178页第74页ChainofResponsibility模式

在链的末端是Application的一个实例。该应用不是一个窗口组件,因此Application不是HelpHandler的直接子类。当一个帮助请求传递到这一层时,该应用可提供关于该应用的一般性的信息,或者它可以提供一系列不同的帮助主题。共178页第75页ChainofResponsibility模式共178页第76页ChainofResponsibility模式共178页第77页ChainofResponsibility模式与Command模式区别:

Command模式需要事先协商客户端和服务器端的调用关系,比如1代表start2代表move等,这些都是封装在request中,到达服务器端再分解。

CoR模式就无需这种事先约定,服务器端可以使用CoR模式进行客户端请求的猜测,一个个猜测试验。共178页第78页ChainofResponsibility模式实现:责任链可能是一条直线、一个环链甚至一个树结构的一部分。

ChainofResponsibility模式的最大的一个特点就是给系统降低了耦合性,请求的发送者完全不必知道该请求会被哪个应答对象处理,极大地降低了系统的耦合性。这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。

共178页第79页Interpreter模式问题

一些应用提供了内建(Build-In)的脚本或者宏语言来让用户可以定义他们能够在系统中进行的操作。Interpreter模式的目的就是使用一个解释器为用户提供一个一门定义语言的语法表示的解释器,然后通过这个解释器来解释语言中的句子。共178页第80页Interpreter模式结构共178页第81页Interpreter模式

Interpreter模式中,提供了TerminalExpression和NonterminalExpression两种表达式的解释方式,Context类用于为解释过程提供一些附加的信息(例如全局的信息)共178页第82页Interpreter模式代码示例:classContext{public: Context(){}; ~Context(){};

protected:

private:};共178页第83页Interpreter模式classAbstractExpression{

public:

virtual~AbstractExpression(){};

virtualvoidInterpret(constContext&c){};

protected:

AbstractExpression(){};

private:};共178页第84页Interpreter模式classTerminalExpression:publicAbstractExpression{

public:

TerminalExpression(conststring&statement);

~TerminalExpression(){};

voidInterpret(constContext&c);protected:private:

string_statement;

};共178页第85页Interpreter模式TerminalExpression::TerminalExpression(conststring&statement)

{

this->_statement=statement;

}voidTerminalExpression::Interpret(constContext&c)

{

cout<<this->_statement<<"TerminalExpression“<<endl;

}共178页第86页Interpreter模式classNonterminalExpression:publicAbstractExpression{public:

NonterminalExpression(AbstractExpression*expression,inttimes);

~NonterminalExpression(){};

voidInterpret(constContext&c);protected:private:

AbstractExpression*_expression;

int_times;

};共178页第87页Interpreter模式NonterminalExpression::NonterminalExpression(AbstractExpression*expression,inttimes)

{this->_expression=expression;

this->_times=times;

}voidNonterminalExpression::Interpret(constContext&c)

{

for(inti=0;i<_times;i++)

{

this->_expression->Interpret(c);

}

}共178页第88页Interpreter模式voidmain()

{

Context*c=newContext();

AbstractExpression*te=newTerminalExpression("hello");

AbstractExpression*nte=newNonterminalExpression(te,2);

nte->Interpret(*c);

}共178页第89页Interpreter模式效果:

Interpreter模式提供了一种很好的组织和设计这种解析器的架构。

Interpreter模式中使用类来表示文法规则,因此可以很容易实现文法的扩展。另外对于终结符我们可以使用Flyweight模式来实现终结符的共享。共178页第90页State模式1.意图允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。共178页第91页State模式动机:考虑一个表示网络连接的类TCPConnection。一个TCPConnection对象的状态处于若干不同状态之一:连接已建立(Established)、正在监听(Listening)、连接已关闭(Closed)。当一个TCPConnection对象收到其他对象的请求时,它根据自身的当前状态作出不同的反应。例如,共178页第92页State模式一个Open请求的结果依赖于该连接是处于连接已关闭状态还是连接已建立状态。State模式描述了TCPConnection如何在每一种状态下表现出不同的行为。共178页第93页State模式这一模式的关键思想是引入了一个称为TCPState的抽象类来表示网络的连接状态。TCPState类为各表示不同的操作状态的子类声明了一个公共接口。TCPState的子类实现与特定状态相关的行为。例如,TCPEstablished和TCPClosed类分别实现了特定于TCPConnection的连接已建立状态和连接已关闭状态的行为。共178页第94页State模式共178页第95页State模式TCPConnection类维护一个表示TCP连接当前状态的状态对象(一个TCPState子类的实例)。TCPConnection类将所有与状态相关的请求委托给这个状态对象。TCPConnection使用它的TCPState子类实例来执行特定于连接状态的操作。一旦连接状态改变,TCPConnection对象就会改变它所使用的状态对象。例如当连接从已建立状态转为已关闭状态时,TCPConnection会用一个TCPClosed的实例来代替原来的TCPEstablished的实例。共178页第96页State模式适用性在下面的两种情况下均可使用State模式:•一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。•一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。共178页第97页State模式结构共178页第98页State模式

参与者•Context(环境,如TCPConnection)

—

定义客户感兴趣的接口。共178页第99页State模式—

维护一个ConcreteState子类的实例,这个实例定义当前状态。•State(状态,如TCPState)—

定义一个接口以封装与Context的一个特定状态相关的行为。•ConcreteStatesubclasses(具体状态子类,如TCPEstablished,TCPListen,TCPClosed)—

每一子类实现一个与Context的一个状态相关的行为。共178页第100页State模式协作•Context将与状态相关的请求委托给当前的ConcreteState对象处理。•Context可将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可访问Context。•Context是客户使用的主要接口。客户可用状态对象来配置一个Context,一旦一个Context配置完毕,它的客户不再需要直接与状态对象打交道。•Context或ConcreteState子类都可决定哪个状态是另外哪一个的后继者,以及是在何种条件下进行状态转换。共178页第101页State模式效果1)它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来,容易增加新的状态和转换。2)使状态转换显式化3)State对象可被共享共178页第102页State模式代码示例描述TCP连接。只是TCP协议的一个简化版本,它并未完整描述TCP连接的协议及其所有状态。定义类TCPConnection,它提供了一个传送数据的接口并处理改变状态的请求。共178页第103页State模式共178页第104页State模式定义类TCPState,每一个TCPState操作都以一个TCPConnection实例作为一个参数,从而让TCPState可以访问TCPConnection中的数据和改变连接的状态。共178页第105页State模式共178页第106页State模式TCPConnection将所有与状态相关的请求委托给它的TCPState实例_state。TCPConnection还提供了一个操作用于将这个变量设为一个新的TCPState。TCPConnection的构造器将该状态对象初始化为TCPClosed状态。共178页第107页State模式共178页第108页State模式共178页第109页State模式TCPState为所有委托给它的请求实现缺省的行为。它也可以调用ChangeState操作来改变TCPConnection的状态。共178页第110页State模式共178页第111页State模式TCPState的子类实现与状态有关的行为。一个TCP连接可处于多种状态:已建立、监听、已关闭等等,对每一个状态都有一个TCPState的子类。我们将详细讨论三个子类:TCPEstablished、TCPListen和TCPClosed。共178页第112页State模式共178页第113页State模式共178页第114页State模式TCPState的子类没有局部状态,因此它们可以被共享,并且每个子类只需一个实例。每个TCPState子类的唯一实例由静态的Instance操作得到。每一个TCPState子类为该状态下的合法请求实现与特定状态相关的行为共178页第115页State模式共178页第116页State模式共178页第117页State模式

在完成与状态相关的工作后,这些操作调用ChangeState操作来改变TCPConnection的状态。TCPConnection本身对TCP连接协议一无所知;是由TCPState子类来定义TCP中的每一个状态转换和动作。共178页第118页State模式说明State需要两种类型实体参与:1.statemanager状态管理器,就是开关,如上面例子的Context实际就是一个statemanager,在statemanager中有对状态的切换动作。

2.用抽象类或接口实现的父类,不同状态就是继承这个父类的不同子类。共178页第119页State模式State模式VsStrategy模式区别在于它们所关注的点不尽相同:State模式主要是要适应对象对于状态改变时的不同处理策略的实现,而Strategy则主要是具体算法和实现接口的解耦(decoupling)共178页第120页Flyweight模式问题:在面向对象系统的设计和实现中,创建对象是最为常见的操作。这里面就有一个问题:如果一个应用程序使用了太多的对象,就会造成很大的存储开销。特别是对于大量轻量级(细粒度)的对象,比如在文档编辑器的设计过程中,我们如果为每个字母创建一个对象的话,系统可能会因为大量的对象而造成存储开销的浪费。例如一个字母“a”在文档中出现了10000次,而实际上我们可以让这一万个字母“a”共享一个对象,当然因为在不同的位置可能字母“a”有不同的显示效果(例如字体和大小等设置不同),在这种情况我们可以为将对象的状态分为“外部状态”和“内部状态”,将可以被共享(不会变化)的状态作为内部状态存储在对象中,而外部对象(例如上面提到的字体、大小等)我们可以在适当的时候将外部对象最为参数传递给对象(例如在显示的时候,将字体、大小等信息传递给对象)。共178页第121页Flyweight模式1.意图运用共享技术有效地支持大量细粒度的对象。共178页第122页Flyweight模式适用性一个应用程序使用了大量的对象。•完全由于使用大量的对象,造成很大的存储开销。•对象的大多数状态都可变为外部状态。•如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。共178页第123页Flyweight模式结构共178页第124页Flyweight模式对象图共178页第125页Flyweight模式参与者•Flyweight(Glyph)—

描述一个接口,通过这个接口flyweight可以接受并作用于外部状态。•ConcreteFlyweight(Character)—

实现Flyweight接口,并为内部状态(如果有的话)增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的;即,它必须独立于ConcreteFlyweight对象的场景。共178页第126页Flyweight模式•UnsharedConcreteFlyweight(Row,Column)

—

并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点(Row和Column就是这样)。共178页第127页Flyweight模式•FlyweightFactory—

创建并管理flyweight对象。—

确保合理地共享flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。•Client—

维持一个对flyweight的引用。—

计算或存储一个(多个)flyweight的外部状态。共178页第128页Flyweight模式协作•flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部对象则由Client对象存储或计算。当用户调用flyweight对象的操作时,将该状态传递给它。•用户不应直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这可以保证对它们适当地进行共享。共178页第129页Flyweight模式代码示例:classFlyweight

{

public:

virtual~Flyweight();

virtualvoidOperation(conststring&extrinsicState);

stringGetIntrinsicState();protected:

Flyweight(stringintrinsicState);private:

string_intrinsicState;};共178页第130页Flyweight模式classConcreteFlyweight:publicFlyweight

{

public:

ConcreteFlyweight(stringintrinsicState);

~ConcreteFlyweight();

voidOperation(conststring&extrinsicState);protected:private:

};

共178页第131页Flyweight模式Flyweight::Flyweight(stringintrinsicState)

{

this->_intrinsicState=intrinsicState;

}Flyweight::~Flyweight()

{}voidFlyweight::Operation(conststring&extrinsicState)

{}stringFlyweight::GetIntrinsicState()

{

returnthis->_intrinsicState;

}共178页第132页Flyweight模式ConcreteFlyweight::ConcreteFlyweight(stringintrinsicState):Flyweight(intrinsicState)

{

cout<<"ConcreteFlyweightBuild....."<<intrinsicState<<endl;}ConcreteFlyweight::~ConcreteFlyweight()

{}voidConcreteFlyweight::Operation(conststring&extrinsicState)

{

cout<<“ConcreteFlyweight:内部”<<this->GetIntrinsicState()<<“[外部"<<extrinsicState<<"]"<<endl;

}共178页第133页Flyweight模式classFlyweightFactory

{

public:

FlyweightFactory();

~FlyweightFactory();

Flyweight*GetFlyweight(conststring&key);protected:private:

vector<Flyweight*>_fly;};

共178页第134页Flyweight模式FlyweightFactory::FlyweightFactory()

{}FlyweightFactory::~FlyweightFactory()

{}共178页第135页Flyweight模式Flyweight*FlyweightFactory::GetFlyweight(conststring&key)

{

vector<Flyweight*>::iteratorit=_fly.begin();

for(;it!=_fly.end();it++)

{//找到了,就一起用,^_^

if((*it)->GetIntrinsicState()==key)

{

cout<<"alreadycreatedbyusers...."<<endl;

return*it;

}}

Flyweight*fn=newConcreteFlyweight(key);

_fly.push_back(fn);

returnfn;

}共178页第136页Flyweight模式intmain(intargc,char*argv[])

{

FlyweightFactory*fc=newFlyweightFactory();

Flyweight*fw1=fc->GetFlyweight("hello");

Flyweight*fw2=fc->GetFlyweight("world!");

Flyweight*fw3=fc->GetFlyweight("hello");

return0;

}共178页第137页Flyweight模式代码说明Flyweight模式在实现过程中主要是要为共享对象提供一个存放的“仓库”(对象池),这里是通过C++STL中Vector容器,当然就牵涉到STL编程的一些问题(Iterator使用等)。另外应该注意的就是对对象“仓库”(对象池)的管理策略(查找、插入等),这里是通过直接的顺序遍历实现的,当然我们可以使用其他更加有效的索引策略,例如Hash表的管理策略,当然这些细节已经不是Flyweight模式本身要处理的了。共178页第138页Flyweight模式讨论

我们在State模式和Strategy模式中会产生很多的对象,因此我们可以通过Flyweight模式来解决这个问题。共178页第139页Decorator模式问题

在OO设计和开发过程,可能会经常遇到以下的情况:我们需要为一个已经定义好的类添加新的职责(操作),通常的情况我们会给定义一个新类继承自定义好的类,这样会带来一个问题(将在本模式的讨论中给出)。通过继承的方式解决这样的情况还带来了系统的复杂性,因为继承的深度会变得很深。

而Decorator提供了一种给类增加职责的方法,不是通过继承实现的,而是通过组合。共178页第140页Decorator模式我们通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的.使用Decorator的理由是:这些功能需要由用户动态决定加入的方式和时机.Decorator提供了"即插即用"的方法,在运行期间决定何时增加何种功能.共178页第141页Decorator模式Decorator定义:

动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活.

共178页第142页Decorator模式讨论

Decorator模式和Composite模式有相似的结构图,其区别在Composite模式已经详细讨论过了,请参看相应文档。另外GoF在《设计模式》中也讨论到Decorator和Proxy模式有很大程度上的相似,初学设计模式可能实在看不出这之间的一个联系和相似,并且它们在结构图上也很不相似。实际上,让Decorator直接拥有一个ConcreteComponent的引用(指针)也可以达到修饰的功能,大家再把这种方式的结构图画出来,就和Proxy很相似了!

共178页第143页Decorator模式Decorator模式和Proxy模式的相似的地方在于它们都拥有一个指向其他对象的引用(指针),即通过组合的方式来为对象提供更多操作(或者Decorator模式)间接性(Proxy模式)。但是他们的区别是,Proxy模式会提供使用其作为代理的对象一样接口,使用代理类将其操作都委托给Proxy直接进行。这里可以简单理解为组合和委托之间的微妙的区别了。

共178页第144页Decorator模式Decorator模式除了采用组合的方式取得了比采用继承方式更好的效果,Decorator模式还给设计带来一种“即用即付”的方式来添加职责。在OO设计和分析经常有这样一种情况:为了多态,通过父类指针指向其具体子类,但是这就带来另外一个问题,当具体子类要添加新的职责,就必须向其父类添加一个这个职责的抽象接口,否则是通过父类指针是调用不到这个方法了。这样处于高层的父类就承载了太多的特征(方法),并且继承自这个父类的所有子类都不可避免继承了父类的这些接口,但是可能这并不是这个具体子类所需要的。而在Decorator模式提供了一种较好的解决方法,当需要添加一个操作的时候就可以通过Decorator模式来解决,你可以一步步添加新的职责。共178页第145页意图动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。别名包装器WrapperDecorator模式共178页第146页动机有时我们希望给某个对象而不是整个类添加一些功能。例如,一个图形用户界面工具箱允许你对任意一个用户界面组件添加一些特性,例如边框,或是一些行为,例如窗口滚动。使用继承机制是添加功能的一种有效途径,从其他类继承过来的边框特性可以被多个子类的实例所使用。但这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。一种较为灵活的方式是将组件嵌入另一个对象中,由这个对象添加边框。我们称这个嵌入的对象为装饰。这个装饰与它所装饰的组件接口一致,因此它对使用该组件的客户透明。它将客户请求转发给该组件,并且可能在转发前后执行一些额外的动作(例如画一个边框)。透明性使得你可以递归的嵌套多个装饰,从而可以添加任意多的功能,如下图所示。Decorator模式共178页第147页Decorator模式共178页第148页Decorator模式共178页第149页Decorator模式

适用性以下情况使用Decorator模式在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。处理那些可以撤消的职责。当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。共178页第150页Decorator模式结构共178页第151页Decorator模式

参与者

Component(VisualComponent)

—

定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent(TextView)

—

定义一个对象,可以给这个对象添加一些职责。Decorator

—

维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。

ConcreteDecorator(BorderDecorator,ScrollDecorator)

—

向组件添加职责。共178页第152页Decorator模式

协作Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作。共178页第153页Decorator模式效果Decorator模式至少有两个主要优点和两个缺点:1)比静态继承更灵活2)避免在层次结构高层的类有太多的特征3)Decorator与它的Component不一样4)有许多小对象共178页第154页Decorator模式代码示例以下C++代码说明了如何实现用户接口装饰。我们假定已经存在一个Component类VisualComponent。共178页第155页Decorator模式共178页第156页Decorator模式我们定义VisualComponent的一个子类Decorator,我们将生成Decorator的子类以获取不同的装饰。共178页第157页Decorator模式Decorator装饰由_component实例变量引用的VisualComponent,这个实例变量在构造器

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论