380 likes | 694 Views
第 13 章 设计模式( Design Pattern ). 13 . 2 可重用的面向对象设计模式. 13.2.1 设计模式概述. 1. 面向对象设计的任务: 应用系统设计 工具库设计 框架设计 其中,框架是已形成源代码的可重用软件体系结构。它体现了应用程序的模块组成关系。框架及框架中的各个模块是形形色色各具特色的。. 设计模式( Design Pattern )描述了软件开发过程中若干重复出现的问题的解决方案,这些方案不是由过程、算法等底层程序构造实体实现,而是由软件系统中类与类之间或不同类的对象之间的共生关系组成。
E N D
13.2 可重用的面向对象设计模式 13.2.1 设计模式概述 1. 面向对象设计的任务: 应用系统设计 工具库设计 框架设计 其中,框架是已形成源代码的可重用软件体系结构。它体现了应用程序的模块组成关系。框架及框架中的各个模块是形形色色各具特色的。
设计模式(Design Pattern)描述了软件开发过程中若干重复出现的问题的解决方案,这些方案不是由过程、算法等底层程序构造实体实现,而是由软件系统中类与类之间或不同类的对象之间的共生关系组成。 设计模式可以帮助软件设计人员学习、重用前人的经验和成果。
设计模式的分类整理最早见于Erich Gamma在德国慕尼黑大学的博士论文。 1995年,Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides合著的 《Design Patterns:Elements of Reusable Object_Oriented Software》系统地整理和描述了23个精选的设计模式(goF模式),为设计模式的学习、研究和推广提供了良好的范例。
类的表示: 子类关系: ClassName BaseClass Function1() Function2() … DataMenber1 DataMenber2 … Subclass 对象及对象链接: aObject anotherObject 2.设计模式的描述符号 (1)类和对象的表示
对象创建关系: ClassA ClassB (2)连接的表示 例如:由类A创建类B的对象 示例代码: classA::create() { return new ClassB;}
(引用名可选) ClassA ClassB Window Rectangle Area() Area() width height refrenceName 实例引用关系: 引用关系涉及到的两个类的实例之间可以形成一种代理关系,接受请求的对象将操作委托给它的代理者: aRect Window::Area() {return aRect→Area();} Rectangle::Area() { return width* height;}
对象聚合关系: ClassA objects ClassB 如:下图表示类A中聚合了多个类B的对象。 聚合关系可以用对象成员实现,但更经常的是将聚合的成员定义为对象成员指针或引用。 由于引用关系也是以对象成员指针或引用来实现的,注意从设计意图上区分对象聚合关系和引用关系对理解设计模式是至关重要的。聚合是对象的包容关系,容器和容器中的对象具有相同的生命期。 引用关系又称相识关系,是一种较松散的耦合关系。具有引用关系的对象,仅仅是知道被引用者的存在,并不为对方负责,
1.抽象工厂(Abstract Factory)模式 抽象工厂定义一个抽象基类,为创建组合对象提供接口。 在面向对象系统中,单个对象的创建由构造函数负责。一个组合对象的动态创建可由一个创建函数一次性完成,以保证被创建的组合对象的完备性。 抽象工厂定义的接口称为创建函数或初始化函数。 抽象工厂的不同实现类(可称为实现工厂)的对象可以按不同的风格去实现组合对象的具体创建,可以在保证组合对象被完备创建的前提下,简化组合对象的版本替换、升级换代过程。
工厂方法(Factory Method)是一些动态创建对象的方法。是在抽象工厂中声明的一组虚函数,它们负责组合对象中成员对象的创建,其实现代码在实现工厂中定义。 抽象工厂模式通常与工厂方法配合使用
ProductFactory ProductCreate() FactoryMethod1() FactoryMethod2() ConcreatB ConcreatA FactoryMethod1() FactoryMethod2() FactoryMethod1() FactoryMethod2() Product1 Product2 Part11 Part21 Part12 Part22 图13.1 抽象工厂和工厂方法模式结构举例
1.创建函数: 一个抽象的产品工厂类ProductFactory定义了由两个零件组成的组件产品的创建函数Product* ProductCreate (); 以及创建零件的工厂方法。 抽象的产品工厂的实现工厂ConcreteProduct重定义了创建零件对象的工厂方法。 创建函数Product* ProductCreate ()调用工厂方法,一次性创建产品。返回产品指针。
2.产品类与零件类 产品类Product与零件类Part可以是分别定义的类,产品类以零件类为自己的对象成员。ProductCreate ()创建一个由2个Part1和1个Part2组合而成的Product组合对象。 如果将Product定义为抽象基类,且Part1和Part2定义为它的实现类,则可以得到更加复杂的Product组合关系。
class Product; class Part1; class Part2; class ProductFactory { //抽象工厂定义 Public: Product* ProductCreate (); Virtual Part1*FactoryMethod1(){}; Virtual Part2*FactoryMethod2(){}; //… }; class ConcreteA: public ProductFactory { //实现工厂定义 Public: Virtual Part1*FactoryMethod1(){ return new Part1; }; Virtual Part2*FactoryMethod2(){ return new Part2; }; //… }; Product* ProductFactory:: ProductCreate () { Product* ptr; Part1* p11= FactoryMethod1(); Part1* p12= FactoryMethod1(); Part1* p21= FactoryMethod2(); Ptr=p11; Return ptr; }
3.用工厂对象参数化组合对象创建过程: 工厂对象即实现工厂的实例对象。 修改生产函数ProductCreate()如下: Product* ProductCreate ( ProductFactory* );//创建函数以抽象工厂的指针为形参。 若再定义一个抽象工厂的实现类: class ConcreteB: public ProductFactory 然后声明: ConcreteA* FactoryA; ConcretetB* FactoryB; 则调用: ProductCreate (FactoryA); ProductCreate (FactoryA ); 将生产A、B两个不同系列的产品。很容易实现产品系列的更新换代。如:Product和Part分别代表电脑主机和配件,产品更新换代只要定义新的抽象工厂的实现类,并用它来参数化生产函数即可,不必对系统的其它部分作任何改动。
class Application{ static Application * instance; Application ( ){}; public: static int count; ~Application ( ); static Application* init(); }; Application *Application::instance=NULL; int Application::count=0; Application *Application::init() { if (count==0) { count=count+1; instance = new Application; cout<<"Single instance has created, OK!!!"<<endl; return instance; } else { cout<<"Can't create more than one instance!!!"<<endl; return NULL; } } Application::~Application() { count=count-1; cout<<"The single instance has released!"<<endl; } 例:单实例对象模式 Application类的定义为私有的,只能通过公有成员函数init()间接访问。
2.递归组合(Composite)模式: 递归组合模式简称组合模式,通过对象递归组合,形成“对象树”,以表示“整体-部分”关系的层次结构。与体现继承性的“类树”结构不同。组合模式提供了一种构造结构复杂的大对象的手段。 组合模式中含有两种类型的对象:基元对象和组合对象。组合模式使组合对象和基元对象具有一致的使用方式。
Picture Graphic Draw() Add(Graphic) Delete(Graphic) GetChild(int) Draw() Add(Graphic) Delete(Graphic) GetChild(int) Rectangle Line Text Draw() Draw() Draw() 例如在图形编辑器应用程序中,按下图所示组合图形编辑对象,可以按用户意愿最终得到任意大小的图形对象组合文件。 图中Line, Rectangle, Text是产生基元对象的类,而Picture是一个组合对象类。 图13.2 图形编辑器中的递归组合
递归组合模式的关键是一个抽象类,它既可以代表图元,又可以代表图元的容器。在上图中这个类就是Graphic,它声明一些与特定图形对象相关的操作,例如Draw()。同时它又声明了所有的组合对象共享的一些操作,例如一些操作用于访问和管理它的子部件。递归组合模式的关键是一个抽象类,它既可以代表图元,又可以代表图元的容器。在上图中这个类就是Graphic,它声明一些与特定图形对象相关的操作,例如Draw()。同时它又声明了所有的组合对象共享的一些操作,例如一些操作用于访问和管理它的子部件。 Graphic的子类Line,Rectangle,Text实现Draw(),以完成各自的绘图功能。基元对象不含子部件,它们都不执行与子部件有关的功能。 Picture类是一个聚合Graphic对象的类。它的Draw()操件是通过对它的子部件调用Draw()实现的。同时它还定义了一些用于访问和管理它的子部件的其它操作。
aPicture aRecttangle aLine aPicture aText aLine aRecttangle 图13.3 递归组合的对象树 Graphic可以产生如下所示的对象树形组合结构: 递归组合模式的对象一般用工厂方法创建。
class Picture; class Graphic { Public: Virtual void Draw()=0; Virtual void Add(Graphic){}; Virtual void Delete(Graphic){}; Virtual Picture *GetPicture (){return 0;} }; class Picture: public Graphic { Public: void Draw(); void Add(Graphic); void Remove(Graphic); Picture *GetPicture (){return this;} }; class Rectangle: public Graphic { Public: void Draw(); }; 为了识别一个组件是基元还是组合,可按以下示例代码,定义一个查询函数GetPicture(),对其返回的组合安全地执行Add()和Remove()操作:
为了识别一个组件是基元还是组合,可按以下示例代码,定义一个查询函数GetPicture(),对其返回的组合安全地执行Add()和Remove()操作:为了识别一个组件是基元还是组合,可按以下示例代码,定义一个查询函数GetPicture(),对其返回的组合安全地执行Add()和Remove()操作: 查询函数GetPicture ()测试代码如下: Picture* aPicture = new Picture; Rectangle* aRectangle = new Rectangle; Graphic* aGraphic; Picture* test; aGraphic = aPicture; if (test = aGraphic-> GetPicture ()){ test->Add(new Rectangle); } aGraphic = aRectangle; if (test = aGraphic-> GetPicture ()){ test->Add(new Rectangle); //此次Add ()操作实际并不执行 }
3.共享对象(Flyweight)模式: 细粒度对象通常因为数量太大而难以用对象进行建模。共享对象模式提供解决这个难题的有效手段。 例如:在文档编辑器程序中,文档对象可能是一个递归组合模式对象。若将文档中的字符作为文档组合对象树的叶结点,可以将字符与文档中的图形、表格等嵌入部分的绘制和格式化作统一处理,并且在程序扩展,支持新字符集时不影响其它部分。但是成千上万的字符对象将耗费大量内存,并产生难以接受的运行开销。 共享对象模式解决了这一问题,使大量细粒度对象的使用无须付出这些过于昂贵的代价。
一个共享对象(flyweight)可以在多个上下文(context)对象中使用。在每个具体的上下文对象中,flyweight作为一个独立的对象出现,在这一点上与非共享对象没有区别。Flyweight的数据成员被看作它的“内部状态”,是只读的。Flyweight的上下文对象为flyweight提供上下文信息,它们被看作flyweight的“外部状态”。用户对象负责在必要的时候将外部状态传递给Flyweight。一个共享对象(flyweight)可以在多个上下文(context)对象中使用。在每个具体的上下文对象中,flyweight作为一个独立的对象出现,在这一点上与非共享对象没有区别。Flyweight的数据成员被看作它的“内部状态”,是只读的。Flyweight的上下文对象为flyweight提供上下文信息,它们被看作flyweight的“外部状态”。用户对象负责在必要的时候将外部状态传递给Flyweight。 以文档编辑器应用为例,它为字母表中每一个字母创建一个共享对象(flyweight)。每个flyweight虽然只存储一个字母代码,但它在文档中的位置和排版风格可以由排版算法或格式化命令决定。
aColumn aRow aRow aRow A m e r i c a flyweight 对象池 a b c d e f g h i j k l m n o p q r s t u v w x y z 逻辑上,文档中的字符每次出现都会有一个对象与之对应。物理上,同一字符共享一个flyweight对象,即同一字符每次出现都指向同一实例,该实例位于对象的共享池中。
4.代理(Proxy)模式 代理模式的原型可以参考文件缓冲区与磁盘文件的关系。正如只有在需要访问某个磁盘文件时才为它建立缓冲区的道理一样,在面向对象系统中只有在确实需要某个对象时才对它进行创建和初始化。 例如,当需要打开一个多媒体文档时,如果一次性打开其中包含的所有对象,往往造成不必要的巨大开销,因为这些对象在文档中并不是同时可见的,没有必要同时创建它们。此时有必要的是在文档中为这些暂时不需要创建的对象设置一个开销极小的代理对象。由它负责在需要时创建由它代理的主体对象。
(b)运行时刻的对象代理关系 图13.5 代理模式的结构和对象间的关系 Client realSubject Proxy Request() { … realSubject-> Request(); …} aClient subject aProxy realSubject aRealSubject RealSubject Subject (a) 代理模式结构 Request() … Request() …
一般来说,在需要利用一个更通用的对象指针来代替较为专门化的对象指针的时候就应该使用代理模式。比如,除了以上所述的多媒体对象的代理外,还可以为不同地址空间的对象提供本地代理,为需要提供多种访问权限的对象提供保护代理等等。一般来说,在需要利用一个更通用的对象指针来代替较为专门化的对象指针的时候就应该使用代理模式。比如,除了以上所述的多媒体对象的代理外,还可以为不同地址空间的对象提供本地代理,为需要提供多种访问权限的对象提供保护代理等等。
智能代理是代理模式的高级形式。 普通代理通过简单的指针替换实现代理任务,智能代理还可以在访问被代理对象时执行一些附加的操作,如: 在第一次引用一个持久对象时,将它装入内存; 在访问一个实际对象前,检查它是否被加锁,以防止冲突; 对对象进行引用计数管理,当对象不再被引用时自动将它释放。
智能代理的一种具体实现方式是重载C++的指针运算符->。这样就可以在通过指针访问被代理对象时,执行一些附加的操作。智能代理的一种具体实现方式是重载C++的指针运算符->。这样就可以在通过指针访问被代理对象时,执行一些附加的操作。
13.2.3 与对象行为相关的模式 行为(behavior)是指对象对请求的可预知反应。 与对象行为相关的模式描述对象之间的通信关系,处理各种在运行时难以跟踪的复杂控制流,描述一组对象怎样相互协作以完成其中任一对象都无法单独完成的任务。
5.职责链(Chain of Responsibility)模式: 职责链模式建立一些对象间的链接关系,使请求沿着该链传递,直到链上的某一个对象处理它为止。链上的多个对象都有机会接收和处理请求,从而避免请求的发送者和接收者之间的耦合关系。 例如图形用户界面中的上下文有关帮助机制,用户可在当前界面以多种方式提交请求,启动帮助。帮助信息窗口以含有进一步帮助的链接信息。这种链接式对象请求处理通常从第一个接收请求的对象开始,或者自己处理该请求,或者转发给链中下一个候选对象。
successor Client Handler Request() Concreat1 Concreat2 Request() Request() aClient aHandler aConcreat successor aConcreat successor (a) 职责链结构 图13.6 职责链模式结构及其对象结构 (b) 职责链对象链接
实现职责链的代码示例: class Handler{ public: Handler(Handler* sp): successor(sp){} virtual void Request(); private: Handler* successor; }; 这是一个事件处理句柄接口,其子类可以定义自己的Request()实现。基类的缺省实现如下: virtual void Handler::Request() { if (successor) successor-> Request(); } 这是一个事先设定的固定的链,只要后继存在就会无条件转发。若在Request()中用不同的请求参数来分派请求,可以实现更灵活的链接。 void Request(AbstrctRequest* theRequest) { switch(theRequest->Getkind()) { case 1: Request((AbstrctRequest *) theRequest); break; case 2: //… default: //… break; } }
6.命令(Command)模式 命令模式又称事务(Transaction)模式,命令模式用于封装向某个对象的请求。 所谓请求,就是应用程序的操作人员通过图形用户界面(GUI)构件如按钮、图标、菜单项等发出的操作命令。 命令模式通过在请求调用对象和请求的执行对象之间增加一个Command中间对象,用以消解多对多复杂性。
Trigger Clicked() {aCommand→Execute()} Command Receiver ConcreateCommand Execute() {aReceive→Action()} Execute() Action() Client aReceiver state 一个触发器(Ttrigger)可以是任何一种GUI控件,不同的触发器对象可以发出同一条命令 一个GUI控件在设计时不可能知道该控件的作用者对象。如一个磁(存)盘图标按钮本身并不需要知道存盘命令执行时是对一个文本文件存盘还是对一个图形文件存盘。命令模式通过将请求本身变成一个对象,使得工具箱对象可向未指定的应用对象发请求,以不同的请求对客户参数化。使多种类型的控件共享抽象类Command的某个子类的一个实例。
Command SaveCommand PasteCommand Execute() Execute() Execute() QuitCommand Execute() 代理 If document is modified save→Execute() else quit the application 命令模式的关键是抽象的Command类。它定义了一个抽象接口,其中最主要的是Execute()操作。Command的实现类将请求的接收者作为实例变量,通过重定义的Execute()指定接收者应该完成的动作。比如,一个Paste命令以一个文档编辑器作为其接收者。
commands Command Execute() ConcreateCommand Execute() { for all c in commands c→Execute() } 命令模式也是实现实现事务功能的基础。一个事务可以看作一条宏命令,即一个请求触发封装在一起的多个命令。宏命令可以对Command类用递归组合模式实现。 图13.9 宏命令递归组合 除了上述特点外,命令模式还可以配合其它模式实现: (1)上下文相关菜单(动态绑定请求); (2)实现撤消重做功能:在Command接口中,除了Execute()外还可定义Unexecute()等方法。