您的位置:首页 > 编程语言 > C语言/C++

【设计模式】--C++设计模式类库 Loki介绍与用法

2012-05-29 14:11 936 查看

http://www.usidcbbs.com/simple/?t2428.html http://book.douban.com/subject/1119904/
C++设计模式类库 Loki介绍与用法

Loki是由Andrei编写的一个与《Modern C++ Design》(C++设计新思维)一书配套发行的C++代码库。

它不仅把C++模板的功能发挥到了极致,而且把类似设计模式这样思想层面的东西通过库来提供。

本篇文章介绍如何利用Loki来轻松地实现一些设计模式。

由于Loki使用了大量牛X到爆的模板技巧,对编译器的要求是很苛刻的,官方兼容列表里只列出了VC7.1以上版本及GCC3.4以上版本。如果你象我一样喜欢用C++Builder6或VC6,可以去下载《Modern C++ Design》配套源码,那里面的Loki提供了对其它不兼容编译器的移植代码,只是版本低了一点,有些接口有些差别。

最后,BTW,《Modern C++ Design》确实是一本好书,在这里也顺便推荐一下^_^

Loki的下载地址是http://sourceforge.net/projects/loki-lib/,目前最新版本是Loki 0.1.7,后面的代码都使用这个版本作为测试标准。


编译

Loki库提供了N多种编译途经,你可以直接打开项目文件(VC、Code::Block、Cpp-Dev等IDE)编译,也可以用传统的makefile来make,还可以直接用批处理文件编译。象我这种被IDE惯坏的人,一般都是直接把src目录里的代码加入到项目中了事。


Singleton模式(单件模式)

Singleton模式确保一个类在系统中只有一个实例。比如一个窗口系统中只能有一个鼠标对象,只有一个屏幕对象,一个剪切板对象...。我们可以用一个全局变量来做这些工作,但它不能防止实例化多个对象。一个更好的办法是让类自身保存它的唯一实例,并且不允许创建其它实例,这就是Singleton模式。

Loki库的SingletonHolder类提供了对Singleton模式的支持


头文件

#include <loki/Singleton.h>


类型定义

template<

typename T,

template< class > class CreationPolicy = CreateUsingNew,

template< class > class LifetimePolicy = DefaultLifetime,

template< class, class > class ThreadingModel = ::Loki::SingleThreaded,

class MutexPolicy = ::Loki::Mutex>

class Loki::SingletonHolder;

Loki的类大部分都是基于策略编程的,其中最主要的是CreationPolicy,它决定了怎样生成一个类实例,可选的有:

template<template<class> class Alloc> struct CreateUsing; 在分配器分配的内存中生成实例,如
template <class T> struct CreateStatic 生成静态实例
template <class T> struct CreateUsingMalloc 使用malloc申请内存并在其中生成实例
template <class T> struct CreateUsingNew 使用new生成实例(默认)


示例代码

class MyClass{

public:

// 有默认构造

MyClass(){;}

// 显示自己所在的内存地址,用以区分是否是同一个对象

void ShowPtr()

{

std::cout << this << std::endl;

}

};

// 定义Singleton的MyClass

typedef Loki::SingletonHolder<MyClass> MyClassSingleton;

int _tmain(int argc, _TCHAR* argv[])

{

// 通过Instance()静态方法取得MyClass实例

MyClass& v = MyClassSingleton::Instance();

v.ShowPtr();

// MyClassSingleton::Instance()总是返回同一个MyClass实例

MyClassSingleton::Instance().ShowPtr();

return 0;

}

Loki::SingletonHolder默认的CreationPolicy策略要求类必须有默认构造,如MyClass这样。如果需要包装没有默认构造的类的话,我们就得自定义一个CreationPolicy策略,好在CreationPolicy策略比较简单,先看看Loki中默认的CreateUsingNew吧:

template <class T> struct CreateUsingNew

{

static T* Create()

{ return new T; }

static void Destroy(T* p)

{ delete p; }

};

呵呵,简单吧,只是简单的Create和Destroy而已。

我们只要修改Create()静态方法,new一个自己的对象就可以了,当然随便多少构造参数都可以在这里写上去啦。另外,如有必要,也可以做一些其它初始工作哦。

class MyClass2{

public:

// 构造里要求两个整数

MyClass2(int,int){;}

void ShowPtr()

{

std::cout << this << std::endl;

}

};

// 我们自己的CreationPolicy策略

template<class T>

class CreateMyClass2UsingNew:

public Loki::CreateUsingNew<T>

{

public:

static T* Create()

{ return new T(0,0); }

};

// 定义使用CreateMyClass2UsingNew策略的Singleton类

typedef Loki::SingletonHolder<MyClass2,

CreateMyClass2UsingNew> MyClass2Singleton;

// 使用之

int _tmain(int argc, _TCHAR* argv[])

{

MyClass2Singleton::Instance().ShowPtr();

MyClass2Singleton::Instance().ShowPtr();

return 0;

}

usidc52011-01-18 16:48


对象工厂 Object Factory

又名简单工厂模式,貌似不属于设计模式范畴。它的作用是把对象的创建工作集中起来,并使创建工作与其它部分解耦。比如下面这个函数也可当作简单工厂:

CWinBase* Create(string s)

{

if(s == "Edit")

return new CEdit;

else if(s == "Button")

return new CButton;

...

}

Loki库的Factory类提供了对简单工厂模式的支持。


头文件

#include


类型

template<

class AbstractProduct, // “产品”基类型

typename IdentifierType, // 用什么区分各产品

typename CreatorParmTList = NullType, // 生成器参数

template< typename, class > class FactoryErrorPolicy = DefaultFactoryError>

class Loki::Factory;


成员方法

bool Register(const IdentifierType& id, ProductCreator creator);以id作为识别码注册生成器。函数、对象方法或仿函数都可以作为生成器。
bool Unregister(const IdentifierType& id);取消注册
std::vector RegisteredIds();取得已注册的所有识别码
AbstractProduct* CreateObject(const IdentifierType& id);

AbstractProduct* CreateObject(const IdentifierType& id, Parm1 p1);

AbstractProduct* CreateObject(const IdentifierType& id, Parm1 p1, Parm2 p2);

...
按识别码id生成对象(调用对应的生成器)


示例代码

#include

#include

#include

#include

// 窗体基类

struct IWidget{

virtual void printName() = 0;

virtual ~IWidget(){;}

};

// 定义窗体工厂,使用string区分各对象类型

typedef Loki::Factory widget_factory_t;</iwidget, std::string>

// 按钮窗体

struct CButton : IWidget{

void printName()

{

std::cout << "CButton" << std::endl;

}

};

// 编辑框窗体

struct CEdit : IWidget{

void printName()

{

std::cout << "CEdit" << std::endl;

}

};

// 列表框窗体

struct CListBox : IWidget{

void printName()

{

std::cout << "CListBox" << std::endl;

}

};

int _tmain(int argc, _TCHAR* argv[])

{

// 工厂实例

widget_factory_t wf;

// 注册各种窗体的生成器,这里偷懒用了CreateUsingNew作为生成器

wf.Register("Edit", Loki::CreateUsingNew::Create );

wf.Register("Button", Loki::CreateUsingNew::Create );

wf.Register("ListBox", Loki::CreateUsingNew::Create );

// 测试,使用工厂生成窗体

{

IWidget* pWid = wf.CreateObject("Edit");

pWid->printName();

delete pWid;

}

{

IWidget* pWid = wf.CreateObject("ListBox");

pWid->printName();

delete pWid;

}

return 0;

}

很多时候,工厂往往只需要一个实例,我们可以使用前面说过的SingletonHolder把widget_factory_t弄成Singleton模式。

上面生成CButton之类的窗体时使用的是默认构造,所以我偷懒没写各个类的生成器,直接用了Loki::CreateUsingNew。

如果你的类有构造参数的话,那么就得在Loki::Factory模板参数中指出参数类型,并且自定义生成器,就象这样:

#include

#include

#include

// 窗体基类

struct IWidget{

virtual void printName() = 0;

virtual ~IWidget(){;}

};

// 定义窗体工厂,这里用Loki::Seq指定参数类型

typedef Loki::Factory<

IWidget,

std::string,

Loki::Seq<std::string, int</std::string, , char>

> widget_factory_t;

// 单件模式,注意Lifetime策略要选择Loki::LongevityLifetime::DieAsSmallObjectChild,否则...

typedef Loki::SingletonHolder<widget_factory_t, loki::createusingnew,< span=""></widget_factory_t, loki::createusingnew,<>

Loki::LongevityLifetime::DieAsSmallObjectChild> Singleton_Fac;

// 按钮窗体

struct CButton : IWidget{

void printName()

{

std::cout << "CButton:" << m_txt << std::endl;

}

CButton(std::string txt, int, char) //多个构造参数

:m_txt(txt){}

std::string m_txt;

};

// 编辑框窗体

struct CEdit : IWidget{

void printName()

{

std::cout << "CEdit:" << m_txt << std::endl;

}

CEdit(std::string txt, int, char) //多个构造参数

:m_txt(txt){}

std::string m_txt;

};

// 列表框窗体

struct CListBox : IWidget{

void printName()

{

std::cout << "CListBox:" << m_txt << std::endl;

}

CListBox(std::string txt, int, char) //多个构造参数

:m_txt(txt){}

std::string m_txt;

};

// 自定义的窗体构造器,用模板只是为了方便点^_^

template<class T> struct CreateT

{

T * operator()(std::string txt, int p1, char p2) const

{

return new T(txt, p1, p2);

}

};

int _tmain(int argc, _TCHAR* argv[])

{

// 工厂实例

widget_factory_t& wf = Singleton_Fac::Instance();

// 注册各种窗体的生成器,用我们的生成器

wf.Register("Edit", CreateT() );

wf.Register("Button", CreateT() );

wf.Register("ListBox", CreateT() );

// 测试,使用工厂生成窗体

{

IWidget* pWid = wf.CreateObject("Edit", "Hello", 0, ' ');

pWid->printName();

delete pWid;

}

{

IWidget* pWid = wf.CreateObject("ListBox", "World", 0, ' ');

pWid->printName();

delete pWid;

}

return 0;

}

Loki::Seq是一个类似于TypeList的东东,可以存放一系列的类型。另外用SingletonHolder包装Factory时,一定要用Loki::LongevityLifetime::DieAsSmallObjectChild作为SingletonHolder的lifttime策略(Loki使用说明上说的,由于Factory使用了Loki内部的内存管理器SmallObject)。
usidc52011-01-18 16:49


Abstract Factory模式(抽象工厂)

抽象工厂提供了一个创建一系列相关或相互依赖对象的接口,而无需指定具体的类。

比如要编写一个可换肤的用户界面,那么我们生成的每个窗体都应该遵循统一的风格,不应该在一个界面里同时出现Mac风格和Win风格的按钮。为了便于控制,我们可以这样写代码:

struct IWidgetFactory{

virtual IButton* CreateButton() = 0;

virtual IEdit* CreateEdit() = 0;

virtual IListBox* CreateListBox() = 0;

};

struct CWindowsFactory : IWidgetFactory{

virtual IButton* CreateButton(){生成Win风格按钮;}

virtual IEdit* CreateEdit(){生成Win风格编辑框;}

virtual IListBox* CreateListBox(){生成Win风格列表框;}

};

struct CMacFactory : IWidgetFactory{

virtual IButton* CreateButton(){生成Mac风格按钮;}

virtual IEdit* CreateEdit(){生成Mac风格编辑框;}

virtual IListBox* CreateListBox(){生成Mac风格列表框;}

};

这样,在程序中我们要用执有IWidgetFactory指针,在必要时实例化某个具体风格的工厂类,最后所有的窗体都由这个IWidgetFactory指针来生成即可。这就是Abstract Factory模式。Loki库的AbstractFactory和ConcreteFactory提供了对Abstract Factory模式的支持。


头文件

#include


类型

template
<
class TList,
template <class>
class Unit = AbstractFactoryUnit
>
class AbstractFactory;

AbstractFactory模板类是一个虚类,它根据TList中的类型提供一组Create<>()方法。

模板参数TList是一个Typelist,输入所有工厂可生产的产品基类,如前面的IButton,IEdit,IListBox。
模板参数Unit依据TList中的所有类型产生对应的Create<>()虚函数,一般直接用默认的AbstractFactoryUnit就可以了。

template
<
class AbstractFact,
template <class, class>
class Creator = OpNewFactoryUnit,
class TList = typename AbstractFact::ProductList
>
class ConcreteFactory;

ConcreteFactory实现了AbstractFactory中的Create<>()方法

模板参数AbstractFact就是对应的AbstractFactory类
模板参数Creator是产品实例的生成策略,可选的有OpNewFactoryUnit和PrototypeFactoryUnit。

OpNewFactoryUnit使用new生成新的产品实例;
PrototypeFactoryUnit使用已有产品(原型)的Clone()方法生成新的实例,这也意味着要使用PrototypeFactoryUnit我们的产品基类必须要有T *Clone()成员方法。

TList提供一组具体的产品类型,如果Creator策略是PrototypeFactoryUnit,可以不提供。


演示代码

#include

#include

#include

// 产品基类

struct IButton{

virtual void click() = 0;

};

struct IEdit{

virtual void edit() = 0;

};

struct IListBox{

virtual void scroll() = 0;

};

// 抽象工厂

typedef Loki::AbstractFactory<

LOKI_TYPELIST_3(IButton, IEdit, IListBox) //也可以用Loki::Seq< IButton, IEdit, IListBox >::Type

> IWidgetFactory;

// 具体产品-Win

struct CWinBtn : IButton{

virtual void click(){

std::cout<< "CWinBtn Clicked" << std::endl;

}

};

struct CWinEdit : IEdit{

virtual void edit(){

std::cout<< "CWinEdit Editing" << std::endl;

}

};

struct CWinLB : IListBox{

virtual void scroll(){

std::cout<< "CWinLB Scrolling" << std::endl;

}

};

// 具体产品-Mac

struct CMacBtn : IButton{

virtual void click(){

std::cout<< "CMacBtn Clicked" << std::endl;

}

};

struct CMacEdit : IEdit{

virtual void edit(){

std::cout<< "CMacEdit Editing" << std::endl;

}

};

struct CMacLB : IListBox{

virtual void scroll(){

std::cout<< "CMacLB Scrolling" << std::endl;

}

};

// 具体工厂

typedef Loki::ConcreteFactory<

IWidgetFactory,

Loki::OpNewFactoryUnit,

LOKI_TYPELIST_3(CWinBtn, CWinEdit, CWinLB)

> CWindowsFactory;

typedef Loki::ConcreteFactory<

IWidgetFactory,

Loki::OpNewFactoryUnit,

LOKI_TYPELIST_3(CMacBtn, CMacEdit, CMacLB)

> CMacFactory;

// 使用工厂生成的产品

void UsingWidget(IWidgetFactory *pFac)

{

IEdit* pEdt = pFac->Create();

IButton* pBtn = pFac->Create();

IListBox* pLB = pFac->Create();

pEdt->edit();

pBtn->click();

pLB->scroll();

delete pEdt;

delete pBtn;

delete pLB;

}

int _tmain(int argc, _TCHAR* argv[])

{

// 使用Win风格

{

CWindowsFactory winfac;

UsingWidget(&winfac);

}

// 使用Mac风格

{

CMacFactory macfac;

UsingWidget(&macfac);

}

return 0;

}

usidc52011-01-18 16:49


Visitor 模式(访问者模式)

访问者模式把对某对象结构中的各元素的相关操作集中到一起,可以方便地在不改变无素类的前提下添加新的操作。

假设一个XML类,我们可能要打印、保存、转换这个XML数据。实际上这些操作都需要遍历节点,不同的只是操作的方式,我们可以这样写代码:

struct IVisitor;

struct TTextNode{

virtual void accept(IVisitor *visitor){ visitor->visit(this); }

};

struct TCDataNode{

virtual void accept(IVisitor *visitor){ visitor->visit(this); }

};

struct TElementNode{

virtual void accept(IVisitor *visitor){ visitor->visit(this); }

};

struct IVisitor{

virtual void visit(TTextNode*) = 0;

virtual void visit(TCDataNode*) = 0;

virtual void visit(TElementNode*) = 0;

};

struct CSaveVisitor{

virtual void visit(TTextNode*){ 保存文本内容 }

virtual void visit(TCDataNode*){ 保存[[CData内容]] }

virtual void visit(TElementNode*){ 保存<标签> 所有子节点.accept(this) }

};

struct CPrintVisitor{

...

};

当然,这种模式的问题是可能会破坏一些封装性。我们这里不讨论Visitor模式的优劣,先看看Loki是怎么实现这个模式的吧


头文件

#include


类型

template
<
typename R = void,
template <typename, class> class CatchAll = DefaultCatchAll,
bool ConstVisitable = false
>
class BaseVisitable;

可访问类型的基类,如果要让元素能被访问,要必须继承自BaseVisitable。

模板参数R是Accept()方法的返回值。
模板参数CatchAll表示某元素没有对应的访问方法时的反应,默认的DefaultCatchAll是什么也不做。
模板参数ConstVisitable表示Accept()方法是否是const方法。

class BaseVisitor;

所有访问者的基类
template <
class T,
typename R = void,
bool ConstVisit = false>
class Visitor;

访问者实现模板

模板参数T是一个Typelist,表示访问者能接受的所有元素类型
模板参数R是Visit()方法的返回值
模板参数ConstVisit表示Visit()方法是否是const方法。


示例代码

#include

#include

#include

#include

#include

// XML元素

struct TTextNode : Loki::BaseVisitable<>{

LOKI_DEFINE_VISITABLE();

TTextNode(std::string text)

:m_text(text){}

std::string m_text;

};

struct TCDataNode : Loki::BaseVisitable<>{

LOKI_DEFINE_VISITABLE();

TCDataNode(std::string text)

:m_cdata(text){}

std::string m_cdata;

};

struct TElementNode : Loki::BaseVisitable<>{

LOKI_DEFINE_VISITABLE();

std::string m_name;

TElementNode(std::string text)

:m_name(text){}

typedef Loki::BaseVisitable<> visitable_t;

//这里用了Loki::SmartPtr,这是一个智能指针类型

typedef Loki::SmartPtr<

Loki::BaseVisitable<>

> ptr_t;

std::vector< ptr_t > m_childs;

};

// 访问者,必须继承自BaseVisitor

class CPrintVisitor :

public Loki::BaseVisitor,

public Loki::Visitor

{

public:

void Visit(TTextNode& n){std::cout << n.m_text;}

void Visit(TCDataNode& n){std::cout << " << n.m_cdata << "]]>";}

void Visit(TElementNode& n){

std::cout<<std::endl;< span="">

std::cout<< '<' << n.m_name << '>' << std::endl;

for(size_t idx=0, len=n.m_childs.size(); idx<len; idx++ )< span=""></len; idx++ )<>

{

n.m_childs[idx]->Accept(*this);

}

std::cout<< std::endl << " << n.m_name << '>' << std::endl;

}

};

int main()

{

// 构建XML结构

TElementNode root("root");

TElementNode *child1 = new TElementNode("child1");

child1->m_childs.push_back(new TTextNode("hello"));

TElementNode *child2 = new TElementNode("child2");

child2->m_childs.push_back(new TCDataNode("world >_<"));

root.m_childs.push_back( child1 );

root.m_childs.push_back( child2 );

// 用CPrintVisitor访问XML所有元素

CPrintVisitor visitor;

root.Accept(visitor);

}


输出结果

hello

_<]]>


usidc52011-01-18 16:51
大牛Andrei Alexandrescu的《Modern C++ Design》讨论的是C++语言的最前沿研究:generative programming。本书中译版估计得要半年以后才能出来,所以只能靠其所附源码来窥测generative programming了。

  目前,我刚将源码读解了约一半,等全部读完,我会将我的读解注释放出来的。现在,现谈一下我的感想。

   先扯得远一点。C++有两个巨大优点:和C兼容,自由;有两个巨大缺点:和C兼容,复杂。C++极其复杂,很难掌握,而这正是“自由”的代价。C++语言是个多编程风格的语言,它同时支持过程化、基于对象、面向对象、泛型、生成性这5种编程思想,具有极其强大的表达能力,可以方便地将各种设计转化为实现。

  generic Programming的思想精髓是基于接口编程(相对于OOP,连多态所需的基类都不要了),它的技术出发点是选择子,核心技术是:类型推导、类型萃取、特化/偏特化,其成果是STL库:一组通用容器和一组操作于通用容器上的通用算法。

  generative programming的思想精髓是基于策略编程(编译器根据策略自动生成所需代码,由于具有更高的抽象性,所以代码复用度也更高),在Loki库的实现中,目前只使用了递归策略,它的技术出发点是Typelist,核心技术是:类型推导、类型萃取、特化/偏特化、多重继承、类型间去耦合,其成果是Loki库:对设计模式的封装。

  Typelist是一种对类型本身进行存储和管理的技巧,它的源码已经贴过了,我也作了注解,此处不再谈论。

  这是多重继承在COM之后的又一大型运用。多重继承极易发生菱型缺陷,所以Loki库使用了类型间去耦合技术来避免:

   template <typename T>

   struct Type2Type

   {

   typedef T OriginalType;

   };

  经过这样一层转换后,原类型T间的各种转换关系(尤其是继承/派生关系)已不复存在,菱型缺陷不会再发生了。

  Loki库的具体实现相当讲究技巧,设计它非常困难(难度远大于STL库,和Boost库有得一拼啊)。但使用它却非常容易,而且便利显著。由于Loki库提供了对设计模式的封装,所以极大量地丰富了C++语言的表达能力,使的你的设计更容易地转化为实现。

  目前,Loki库只提供了对厂模式和visitor模式的封装,它还处于发展初期。

  我以visitor模式为例,讲解Loki库提供的便利。

  Visitor模式有四种实现方式:1一串RTTI的类型判断;2二次调度(double dispatch);3建立类型与处理函数的对应表;4非循环visitor(Acyclic Visitor)。在《More Effective C++》 Item 31中讨论了前3种实现(虽然它的例子本身不太算visitor模式的)。第四种方式在《使用设计模式改善程序结构》(二)(http://www-900.ibm.com/developerWorks/cn/java/l-dpstruct/part2/index.shtml)中有详细讲解。

Loki库实现图

  使用Loki库,用户实现visitor模式时只需:让Host类从BaseVisitable继承,并在所有派生类中加上DEFINE_VISITABLE()宏,让所有Visitor类从BaseVisitor类和VisitorImpl类进行二重继承(并且可以只提供对自己感兴趣的Host类的处理函数,不感兴趣的不提供处理函数)。用户的工作量非常小、非常简单,设计人员可以不用为实现而分心了。

  更重要的是,由于采用了类型间去耦合技术,多个Host之间存在继承关系时,不会发生问题(其具体实现较复杂,于是我在UML图上作了模糊处理,没有展示出来,留在以后Loki库源码读解时讲述)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: