创建型模式
面向对象的软件开发的基础是对象。随着系统的不断演化,会出现越来越多的对象,如果单纯使用C++提供的new操作符,将使程序中到处都是硬编码的对象创建代码,很难适应变,化。而创建型模式抽象了类的实例化过程,它封装了对象的创建动作,使对象的创建可以独立.于系统的其他部分。
抽象工厂(Abstract Factory)
抽象工厂模式把对象的创建封装在一个类中,这个类唯一的任务就是按需生产各种对象,通过派生子类的方式抽象工厂可以生产不同系列的、整套的对象。工厂类通常是单件,以保证,在系统的任何地方都可以访问,其中的每个方法都是工厂方法。在较小的软件系统中,抽象工,厂有时候会退化成一个没有子类的简单工厂。
xpressive库有一个抽象工厂regex_compiler,可以生产各种正则表达式解析对象,但它用模板技术而不是继承实例化出了具体的工厂。
生成器(Builder)
生成器模式分解了复杂对象的创建过程,创建过程可以被子类改变,使同样的过程可以生产出不同的对象。生成器与抽象工厂不同,它不是一次性地创建出产品,而是分步骤逐渐地“装配”出对象,因而可以对创建过程进行更精细的控制。
Boost库没有生成器模式的具体应用,因为生成器模式主要用来构造复杂的对象,对于库来说复杂的创建过程会令库难以使用。
multi_array对象的创建过程类似生成器模式,它先用模板参数设定基本维数,然后再逐个指定各个维度,最后生成一个多维数组。
工厂方法(Factory Method)
工厂方法模式是另一种生产对象的方式,它把对象的创建封装在一个方法中,子类可以改变工厂方法的生产行为生产不同的对象。工厂方法所属的类不一定是工厂类(抽象工厂或者生成器),它可能是一个普通类、一个框架类,或者是一个自由函数。
Boost 库中的 make_shared () 、make_optional() 、make_tuple ()、regex<>:: complie ()等函数都属于工厂方法模式。而functional/factory组件则实现了对new操作符的完全封装,类似于泛化的make_pair ()、 make_optional()。
原型(Prototype)
原型模式使用类的实例通过拷贝的方式创建对象,具体的拷贝行为可以定制,它最常见的用法是为类实现一个clone()成员函数,这个函数创建,个与原型相同或相似的新对象。
weak_ptr的enable_shared _rom_this用法类似于一个原型模式,它创建了一个指向自身的shared_ptr, exception库提供了一个enable_current_exception()函数,它被用于线程安全地处理异常,返回一个clone的异常对象。
指针容器库pointer_container容纳的指针不允许共享,如果要拷贝指针容器,则需要被容纳的元素提供clone()操作,使用原型模式创建一个等价的副本。
单件(Singleton)
单件模式保证类有且仅有一个实例,并且提供一个全局的访问点。通常的全局变量技术虽,然也可以提供类似的功能,但它不能防止用户创建多个实例。单件的基本原理很简单,但有很,多实现的变化。
Boost库目前没有专门的单件库,但在serialization库提供了一个可用的实现。
结构型模式
结构型模式专注于如何组合类或对象,进而形成更大更有用的新对象。
组合对象有两种方式:第一种是C++语言本身提供的继承机制,但它在编译期就已经确定,了对象的关系,无法在运行时改变,缺乏足够的灵活性。第二种方法是运行时对象组合,不同的对象之间彼此互相独立,仅通过定义良好的接口通信协同工作,它更灵活和易于模块化,但因为组合方式富有变化而较难以理解。
适配器(Adapter)
适配器模式把一个类的接口转换(适配)为另一个接口,从而在不改变原有代码的基础上,复用原代码。它的别名wrapper更清晰地说明了它的实现结构:包装了原有对象,再给出一个新的接口。
array类是适配器模式的一个很好例子,它把原始数组适配成了符合STL标准的容器,
使数组可以与其他标准库组件(算法、迭代器、其他容器)协同工作。同样, multi_ array_ref和const_multi_array_ref把原始数组适配成了Boost的多维数组容器。
thread库的lockable_adapter是适配器类用法的例子,它工作在编译期,把一个类适配为可以被锁定的类型,用于多线程环境编程。
桥接(Bridge )
桥接模式分离了类的抽象和实现,使它们可以彼此独立地变化而互不影响。桥接模式与适配器模式有些相似,在两个对象之间加入了一个中间层次,提供间接联系增加了系统的灵活性。但两者的意图不同,适配器模式关心的是接口不匹配的问题,不关心接口的实现,只要求对象能,够协同工作。桥接模式的侧重点是接口和实现,通常接口是稳定的,桥接解决实现的变化问题。
智能指针的pimpl用法是桥接模式的一个应用,它向外界提供接口的同时并没有暴露任,何内部的实现信息,因此实现可以任意地改变而不会影响到使用接口的客户代码。
桥接模式的另一个例子是随机数库random的硬件随机数发生器。random_device的对外,接口不变,而内部的pimpl指针可以采用不同的硬件设备从而有不同的实现。
组合(Composite)
组合模式将小对象组合成树形结构,使用户操作组合对象如同操作一个单个对象。组合模,式定义了“部分一整体”的层次结构,基本对象可以被组合成更大的对象,而且这种操作是可重复的,不断重复下去就可以得到一个非常大的组合对象,但这些组合对象与基本对象拥有相,同的接口,因而组合是透明的,用法完全一致。
xpressive库就利用了组合模式,它定义了许多小的正则表达式元素,通过重载操作符把它们逐个组合起来,形成一个大的正则表达式,而这些正则表达式又可以被继续组合下去。
property_tree更好地诠释了组合模式。属性树的每一个子节点也是属性树,属性树可以有任意复杂的组合,但最终呈现给用户的还是一个basic ptree接口,使用时完全不需要关心它内部的复杂结构,这是一个透明的接口。
multi_array也是组合模式的一个具体应用,它是递归定义的,每个维度都是一个multi_array。
装饰(Decorator)
装饰模式可以在运行时动态地给对象增加功能。它也是对对象的包装,但与适配器模式不,同的是它并没有改变被包装对象的接口,只是改变了它的能力范围,而且可以递归组合。
通过生成子类的方式也可以为对象增加功能,但它是静态的,而且大量的功能组合很容易,产生“子类爆炸”现象。装饰模式可以动态、透明地给对象增加职责,并且在不需要的时候很,容易去除,使用派生子类的方式无法达到这种灵活程度。
operators库的基类链技术很类似装饰模式,用后一个运算概念装饰前一个运算概念,不断组,合增加了操作符重载的能力,但它是运用泛型编程技术在编译期实现的。
外观(Facade)
外观模式为系统中的大量对象提供一个一致的对外接口,以简化系统的使用。外观是另-种形式的wrapper,它不是包装一个对象,而是包装一组对象,简化了这组对象间的通信关系,给出一个高层次的易用接口。但外观并不屏蔽系统里的对象,如果需要,用户完全可以越过外观的包装使用底层对象以获得更灵活的功能。
随机数库random的变量发生器就是一个外观模式,它屏蔽了random库内部的大量细,节,给用户提供一个可轻松生成随机数的operator ()。
享元(Flyweight)
享元模式使用共享的方式节约内存的使用,可以支持大量细粒度的对象。它将对象的内部,状态与外部状态分离,配合工厂模式(抽象工厂或工厂方法)生成仅有内部状态的小对象,工厂内部保持小对象的引用计数从而实现共享,外部状态可以通过计算得到。
xpressive库的regex_compiler不仅是一个抽象工厂模式,它同时也是享元模式,在内部保存了所有正则表达式对象从而实现共享。Boost里还有一个flyweight库直接实现了享元模式。
代理(Proxy)
代理模式与适配器模式、装饰模式很像,也包装对象,但它的意图不是改变接口插入新系统(适配),也不是为对象增加职责(装饰),而是要控制对象。外界不能直接访问对象,必须通过代理才能与被包装的对象通信。
代理模式的应用非常广泛, smart_ptr库就是代理模式的最佳应用。scoped_ptr.shared_ptr等智能指针包装了原始指针,代理了原始指针的职能,用户只需使用智能指针的代理就可以获得原始指针同样的功能,而且不用担心资源泄漏,因为智能指针控制了原始指针的行为。
bind. optional, ref和function也属于代理模式,它们都包装了原始的对象或者函数,为它们提供一定程度的控制,在需要时把消息转发给原始的对象或函数完成工作。
行为模式
行为模式关注的是程序运行时的对象通信和职责分配,跟踪动态的、复杂的控制流和消息,流,比创建型模式和结构型模式更难于掌握。通常对象一旦创建,它们就立即联系起来,这种联系是动态的,很难甚至不可能从代码中看出来。行为模式以可文档化的形式描述对象通信机,制,可以帮助我们深入了解把握对象之间的关系。
行为模式大都采用对象组合,封装程序的可变部分。
职责链(Chain of Responsibility)
职责链模式把对象链成一条链,使链上的每个对象都有机会处理请求。职责链把请求的发,送者和接收者解耦,使两者都互不知情,而且职责链中的对象可以动态地增减,从而增强了处理请求的灵活性。
assign库的工作原理类似职责链模式,但链上仅有一个对象,它使用重载操作符operator()和operator,,将赋值请求连接成一个链逐个处理,最后完成赋值或初始化工作。
iostreams库也使用了职责链模式,它定义了source, sink, filter等概念,对象,间可以串联起来,一个的输出作为另一个的输入,完成流处理的功能。
命令(Command)
命令模式把请求封装成一个对象,使请求能够存储更多的信息拥有更多的能力。命令模式,同样能够把请求的发送者和接收者解耦,但它不关心请求将以何种方式被处理。命令模式经常!与职责链模式和组合模式一起使用:职责链模式处理命令模式封装的对象,组合模式可以把简单的命令对象组合成复杂的命令对象。
exception库是命令模式的一个例子。它把错误信息包装在异常中,使用c++的异常机制传递,直到有一个catch块处理它。
解释器( Interpreter)
解释器模式定义了个类体系,用1实现,个小型语言的解释器。它与组合模式很相似,而且常常利用组合模式来实现语法树的构建。
regex, xpressive, proto, spirit, wave等库都使用了解释器模式,前两者可以解析正则表达式, proto是一个通用的表达式树构建工具, spirit实现了EBNF语法解析器,
vave则是C/C++的预处理解析器。
program_options也是一个解释器模式应用,它可以解析命令行参数这种简单的语法,结构。
代器(Iterator)
迭代器模式把按某种顺序访问一个集合中的元素的方式封装在一个对象中,从而无须知道,售合的内部表示就可以访问集合。
迭代器模式可能是面向对象软件开发中应用的最广泛的一个设计模式,在STL中就已经有了大量的迭代器实践,它是泛化的指针,被用于以正序或逆序遍历容器内的元素。boost.iterators和boost.range在STL的基础上进一步深化创新了迭代器的概念。
中介者(Mediator)
中介者模式用一个中介对象封装了一系列对象的交互联系,使它们不需要相互了解就可以协同工作。中介者模式在存在大量需要相互通信对象的系统中特别有用,因为对象数量的增加,会使对象间的联系非常复杂,整个系统变得难以理解难以改动。这时中介者可以把这些对象解,耦,每个对象只需要与中介对象通信,中介对象集中了控制逻辑,降低了系统的通信复杂度。
中介者模式与观察者模式是相互竞争的模式,通常观察者模式比中介者模式更容易生成可,复用的对象,中介者模式如果使用不当很容易导致中介对象过度复杂,抵消了模式带来的好处。
Boost库暂没有中介者模式的应用例子。
备忘录(Memento)
备忘录模式可以捕获一个对象的内部状态,并在对象之外保存该状态,在之后可以随时把,|对象恢复到之前保存的状态。
io_state_savers库的实现类似备忘录模式,它可以保存10流的各种状念,在析构时,自动恢复,防止10流因状态异常而发生错误。
观察者(Observer)
观察者模式定义了对象间一对多的联系,当个对象的状态发生变化时,所有与它有联系的观察者对象都会得到通知。观察者模式将被观察的目1标和观察者解糊,个11标可以有任意多的观察者,观察者也可以观察任意多的1标,构成复杂的联系,而每个观察者都不知道其他,观察者的存在。
观察者模式是一个非常著名、威力强大的设计模式,很多编程语言都有它不同形式的实现,
如Java的Observable/observer和C#的event/delegate.Boost库提供signals2,完全实现了观察者模式。
状态(State)
状态模式允许对象在状态发生变化时行为也同时改变。
状态转换通常的做法是对象内部有一个值来保存当前的状态,根据状态的不同使用,if-else或者switch来执行不同的功能。这样会使类中存在大量结构类似的分支语句,变得难以维护和理解。状态模式消除了分支语句,把状态处理分散到了各个状态子类,每个子类集中处理一种状态,使状态的转换清晰明确。
boost.statechat和msm库实现了有限状态自动机,它们是状态模式的泛化。
策略(Strategy)
策略模式封装了不同的“算法”,使它们可以在运行时相互替换。它与结构型模式里的装.饰模式功能接近,策略模式改变类的行为内核,而装饰模式改变类的行为外观。如果类的接口很庞大,那么装饰模式的实现代价就过高,而策略模式仅改变类的内核,可能很小。策略模式,的实现结构很像状态模式,但它不改变对象的状态。
标准库和Boost库中的大量函数对象就是策略模式的应用。函数对象封装了各种操作,标准算法或者其他类使用传入函数对象来动态改变它的行为。
Boost库中大部分组件的模板类型参数也可以看作是策略模式,通过配置不同的模板类,型,最后实例化的模板类内部的算法都不相同。
模板方法(Template Method)
模板方法模式在父类中定义操作的主要步骤,但并不实现,而是留给子类去实现。注意这个模板与C++中泛型编程用的template没有任何联系,不要引起误解。它常见的用法是“钩,子操作”,父类定义了所有的公开方法,在公开方法中调用保护的钩子方法,子类实现不同的,钩子方法来扩展父类的行为。
模板方法模式是一个非常基本的设计模式,也可能是最容易使用的,个设计模式,许多框!架都使用模板方法定义基本的操作步骤,用户只需实现少量的具体化代码就可以利用框架的全部功能。
boost. test定义了单元测试的框架,它使用模板方法模式定义了许多可扩展的方法,用户只需要依据test库的规则编写测试用例,就可以插入到UTF中进行测试。
访问者(Visitor)
访问者模式分离了类的内部元素与访问它们的操作,可以在不改变内部元素的情况下增加,作用于它们的新操作。如果一个类有很多内部数据,因此也就有很多访问操作,这样会使它的接口非常庞大,难以变动难以学习。访问者模式可以做到数据的存储与使用分离,不同的访问,者可以集中不同类别的操作,并且可以随时增加新的访问者或者新方法来增加新的操作。
boost.variant提供static visitor实现了访问者模式,可以对一个很小的variant对象实施各种操作,如果variant对象发生改变, static visitor也可以很容,易地适应变化。
其他模式
推荐书目[11成书于1994年,在那以后软件界开始了广泛的模式运动,在软件开发的各,个领域各个层次都逐步发现了很多新的模式,但最经典最基本的仍然是上面讲到的23个设计模式。本节介绍四个较为常用的其他模式,供读者参考。
空对象(Null Object)
空对象模式又称哑对象模式(Dumb object),它是一个行为模式,扩展了空指针的含义,给空指针一个默认的、可接受的行为,通常是空操作,可以说是一个“智能空指针”。使用空对象模式,程序就可以不必用条件语句专门处理空指针或类似的概念,所有的对象都会有一致的、可理解的行为。
空对象模式可以和许多行为模式配合,充当“哨兵”的角色,完善它们的概念。例如,策略模式有一个空策略对象,它不做任何事情;职责链模式把空对象用在链的末尾,它可以“吞下”所有无法处理的请求;空迭代器对象则适用于叶子节点,表示总完成遍历操作:还可以有空命令、空观察者等等。
在tuple中的null_type就是个空对象,它表示了一个空的tuple,什么也不做,但它非常重要,没有它就无法完成tuple的部件链。
空对象模式还可以应用在pointer container库中,使指针容器可以安全地容纳空指,针,用一致的行为处理容器元素。
空对象模式也不仅用在面向对象的软件构建,操作系统中也有类似的概念,如Linux的,/dev/nul1设备。
对象池(Object Pool)
对象池模式属于创建型模式,可以说是一个特殊的工厂模式:它预先创建好若干个可用的.对象,用户向池申请对象,使用完毕归还池而不是直接销毁,所以归还的对象可以被之后的请,求复用。通过这种方式,对象池模式摊平了昂贵的构造成本,消除了对象的销毁成本,可以提,高系统的整体运行效率。
对象池模式在软件开发中已经得到了广泛的应用,例如耳熟能详的连接池、内存池、线程池等名词。Boost库里的poo1实现了内存池, thread_group则实现了简单的线程池。
包装外观(Wrapper Facade)
包装外观模式很类似外观模式,但它包装的目标不是一个面向对象子系统,而是底层的,API。包装外观模式把大量的原始c接口分类整理,给外界一个统一的、面向对象的易用接口,增强了原始底层接口的内聚性,同时又没有效率的损失(可以使用静态成员函数、名字空间和,inline关键字),包装外观模式可以屏蔽系统底层的细节,简化功能调用,有利于外界不受,平台变化的影响,增强可移植性。
system库是包装外观模式的一个例子,它对UNIX和Windows等操作系统的错误代码,分类包装,对外提供了一个方便使用、易于理解的接口。
atomic. thread, asio库和interprocess也都在不同的层次上使用了包装外观模,式,提供了可移植的并发处理功能。
,
前摄器模式(Proactor)
前摄器模式是应用于异步调用的设计模式,它的核心是前摄器、异步的操作处理器、异步的事件多路分离器和完成事件队列,可以不使用线程实现异步操作。
前摄器模式的基本流程可以简要描述如下:
前摄发起器创建一个完成处理器,用于在异步调用完成后的回调,然后发起个异步操作,交给操作处理器异步执行,当是步操作完成时操作处理器将把事件放入宗成事件队列。前摄器调用多路分离器从完成事件队列中获得事件, 分派事件回调完成处理器执行所需的后续操作。
前摄器模式用于异步调用有很多的好处,它封装了并发机制,将并发机制与线程的执行解耦,简化了功能代码的编写,不需要考虑多线程的同步问题,能够提供高性能的异步操作。但它也有缺点,模式比较复杂,处理流程难以理解和调试。
asio库基于操作系统的异步调用机制实现了可移植的前摄器模式,解耦了应用程序与操作系统,可以高效地实现异步10操作。
总结
本章结合Boost程序库介绍了附录A推荐书目[1]中的23个经典设计模式和4个常用,的其他设计模式。Boost库应用了以上几乎所有的设计模式,因此学习设计模式有助于更好更快地理解Boost组件的结构和用法。
设计模式通常分为三类,创建型模式管理面向对象系统中对象的产生,结构型模式管理!面向对象系统中对象的组合,行为模式管理面向对象系统中对象的通信。
创建型模式是程序的基础,因为面向对象系统就是由许许多多的对象组成的,创建型模式能够使对象的创建独立于系统单独变化。最常用的创建型模式是抽象工厂和单件,而且这两者经常联合使用。
结构型模式把对象组合起来,以获得功能更强大更灵活的对象。使用结构型模式,我们可以避免继承滥用和“子类爆炸”现象,并在不改变原有类的基础上生成许多新的可用对象。最常用的结构型模式有适配器、组合、装饰、外观和代理。
行为模式确定了多个对象间通信与合作的最佳方式,它刻画了对象之间的合作机制,这种合,作机制高效且能够适应未来的变化。常用的行为模式有迭代器、观察者、状态、策略和访问者。
学习设计模式时理解模式的目的、意图和用途很重要,因为编程语言的语法、语义限制,很多设计模式的实现结构很相似甚至完全相同(例如装饰和代理),但不同的设计出发点(意图)和应用领域导致了模式的用法有很大的区别。
设计模式也经常与重构联系在一起,重构会导致应用设计模式,而设计模式的目的是为了避免将来的重构。