命令
一、功能
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
二、示例代码
namespace DesignPattern_Command
{
// class Receiver
class Receiver
{
public:
void SomeOperation() {}
} ;
// class Command
class Command
{
public:
Command() : _r(NULL) {}
virtual void Execute() = 0 ;
void SetReceiver(Receiver* pr) { _r = pr ; }
protected:
Receiver* _r ;
} ;
// class ConcreteCommand
class ConcreteCommand : public Command
{
public:
virtual void Execute()
{
// do some operations
(_r) ? _r->SomeOperation() : 0 ; // receiver do some operations
}
} ;
// class Invoker
class Invoker
{
public:
void AddCommand(Command* pc)
{
assert(pc) ;
_list.push_back(pc) ;
}
void ExecuteCommand()
{
Command* pc = _list.back() ;
pc->Execute() ;
}
void UndoCommand() { /*...*/ }
void RedoCommand() { /*...*/ }
private:
vector< Command* > _list ;
} ;
}
客户端代码:
{
using namespace DesignPattern_Command ;
Receiver r ;
ConcreteCommand c ;
c.SetReceiver(&r) ;
Invoker inv ;
inv.AddCommand(&c) ;
inv.ExecuteCommand() ;
}
中介者
一、功能
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
二、示例代码
namespace DesignPattern_Mediator
{
class Mediator ;
// class Subsystem_ClassA
class Subsystem_ClassA
{
public:
Subsystem_ClassA(Mediator *pMediator) : _mediator(pMediator) {}
void Operation1() ;
void Operation2() { /*...*/ }
private:
Mediator *_mediator ;
} ;
// class Subsystem_ClassB
class Subsystem_ClassB
{
public:
Subsystem_ClassB(Mediator *pMediator) : _mediator(pMediator) {}
void Operation1() ;
void Operation2() { /*...*/ }
private:
Mediator *_mediator ;
} ;
// class Medaitor
class Mediator
{
public:
Mediator() ;
void Proc1() ;
void Proc2() ;
void Test1() { _obja->Operation1() ; }
void Test2() { _objb->Operation1() ; }
private:
Subsystem_ClassA *_obja ;
Subsystem_ClassB *_objb ;
} ;
void Subsystem_ClassA::Operation1()
{
// ...
_mediator->Proc1() ;
// ...
}
void Subsystem_ClassB::Operation1()
{
// ...
_mediator->Proc2() ;
// ...
}
Mediator::Mediator()
{
_obja = new Subsystem_ClassA(this) ;
_objb = new Subsystem_ClassB(this) ;
}
void Mediator::Proc1()
{
// ...
_objb->Operation2() ;
// ...
}
void Mediator::Proc2()
{
// ...
_obja->Operation2() ;
// ...
}
}
客户端代码:
{
using namespace DesignPattern_Mediator ;
Mediator m ;
m.Test1() ;
m.Test2() ;
}
上例中的Mediator封装了Subsystem_ClassA::Operation1()-->Subsystem_ClassB::Operation2()和Subsystem_ClassB::Operation1()-->Subsystem_ClassA::Operation2()的逻辑,而Subsystem_ClassA和Subsystem_ClassB彼此是不可见的。如果未来这种逻辑改变了,Subsystem_ClassA和Subsystem_ClassB都无需改变,只要改Mediator就可以了。
备忘录
一、功能
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
二、示例代码
namespace DesignPattern_Memento
{
class Originator ;
// class Memento
class Memento
{
public:
void GetState(Originator*) { /*...*/ }
void SetState(Originator*) { /*...*/ }
} ;
// class Originator
class Originator
{
public:
Memento* CreateMemento()
{
Memento* p = new Memento() ;
p->SetState(this) ;
return p ;
}
void SetMemento(Memento* p)
{
assert(p) ;
p->GetState(this) ;
}
} ;
}
客户端代码:
{
using namespace DesignPattern_Memento ;
Originator obj ;
Memento *pMem = obj.CreateMemento() ; //保存状态
// ...
obj.SetMemento(pMem) ; //恢复状态
}
观察者
一、功能
定义对象间的一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
二、示例代码
namespace DesignPattern_Observer
{
class Subject ;
// class Observer
class Observer
{
public:
virtual void Update() = 0 ;
void SetSubject(Subject *pSubject) { _subject = pSubject ; }
protected:
Subject *_subject ;
} ;
// class ConcreteObserver
class ConcreteObserver : public Observer
{
public:
virtual void Update() { /* using '_subject' to get data */ }
} ;
// class Subject
class Subject
{
public:
void AddSubject(Observer* pObserver)
{
_list.push_back(pObserver) ;
pObserver->SetSubject(this) ;
}
void Notify()
{
vector< Observer* >::const_iterator it ;
for (it = _list.begin(); it != _list.end(); it++)
(*it)->Update() ;
}
// ...
private:
vector< Observer* > _list ;
} ;
// class ConcreteSubject
class ConcreteSubject : public Subject
{
// ...
} ;
}
客户端代码:
{
using namespace DesignPattern_Observer ;
ConcreteObserver o1 ;
ConcreteObserver o2 ;
ConcreteSubject s ;
s.AddSubject(&o1) ;
s.AddSubject(&o2) ;
s.Notify() ;
}
状态
一、功能
把一个对象的内部状态从对象中分离出来,形成单独的状态对象,所有与该状态相关的行为都放入该状态对象中。
二、示例代码
namespace DesignPattern_State
{
class State ;
// class Context
class Context
{
public:
Context(State *pState = NULL) : _state(pState) {}
void Request1() ;
void Request2() ;
void ChangeState(State* newstate) { assert(newstate) ; _state = newstate ; }
private:
State *_state ;
} ;
// class State
class State
{
public:
virtual void Handle1(Context*) {}
virtual void Handle2(Context*) {}
protected:
State() {}
void ChangeState(Context* c, State* s) { assert(c) ; c->ChangeState(s) ; }
} ;
void Context::Request1() { assert(_state) ; _state->Handle1(this) ; }
void Context::Request2() { assert(_state) ; _state->Handle2(this) ; }
// class ConcreteStateA
class ConcreteStateA : public State
{
public:
static State* Instance() { return &_state ; } //singleton
virtual void Handle1(Context*) ;
virtual void Handle2(Context*) ;
protected:
ConcreteStateA() {}
private:
static ConcreteStateA _state ;
ConcreteStateA(const ConcreteStateA&) ;
} ;
ConcreteStateA ConcreteStateA::_state ;
// class ConcreteStateB
class ConcreteStateB : public State
{
public:
static State* Instance() { return &_state ; } //singleton
virtual void Handle1(Context*) ;
virtual void Handle2(Context*) ;
protected:
ConcreteStateB() {}
private:
static ConcreteStateB _state ;
ConcreteStateB(const ConcreteStateB&) ;
} ;
ConcreteStateB ConcreteStateB::_state ;
void ConcreteStateA::Handle1(Context* c)
{
// do something with using 'c'
ChangeState(c, ConcreteStateB::Instance()) ;
}
void ConcreteStateA::Handle2(Context* c)
{
// do something with using 'c'
ChangeState(c, ConcreteStateB::Instance()) ;
}
void ConcreteStateB::Handle1(Context* c)
{
// do something with using 'c'
}
void ConcreteStateB::Handle2(Context* c)
{
// do something with using 'c'
}
}
客户端代码:
{
using namespace DesignPattern_State ;
Context context ;
context.ChangeState(ConcreteStateA::Instance()) ; //状态1
context.Request1() ; //操作1
context.ChangeState(ConcreteStateB::Instance()) ; //状态2
context.Request2() ; //操作2
}
策略
一、功能
定义一系列的算法,把它们分别封装起来,并使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
二、示例代码
namespace DesignPattern_Strategy
{
// class Strategy
class Strategy
{
public:
virtual void Algorithm() = 0 ;
} ;
// class ConcreteStrategyA
class ConcreteStrategyA : public Strategy
{
public:
virtual void Algorithm() { /*...*/ }
} ;
// class ConcreteStrategyB
class ConcreteStrategyB : public Strategy
{
public:
virtual void Algorithm() { /*...*/ }
} ;
// class ConcreteStrategyC
class ConcreteStrategyC : public Strategy
{
public:
virtual void Algorithm() { /*...*/ }
} ;
// class Context
class Context
{
public:
void SetStrategy(Strategy* pStrategy) { _strategy = pStrategy ; }
void Operation()
{
assert(_strategy) ;
// ...
_strategy->Algorithm() ;
// ...
}
private:
Strategy *_strategy ;
} ;
}
客户端代码:
{
using namespace DesignPattern_Strategy ;
Context c ;
ConcreteStrategyA s1 ;
c.SetStrategy(&s1) ;
c.Operation() ; //使用算法A
ConcreteStrategyB s2 ;
c.SetStrategy(&s2) ;
c.Operation() ; //使用算法B
ConcreteStrategyC s3 ;
c.SetStrategy(&s3) ;
c.Operation() ; //使用算法C
}
模板方法
一、功能
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
二、示例代码
namespace DesignPattern_TemplateMethod
{
// class AbstractClass
class AbstractClass
{
public:
void TemplateMethod()
{
// ...
PrimitiveOperation1() ;
// ...
PrimitiveOperation2() ;
}
virtual void PrimitiveOperation1() = 0 ;
virtual void PrimitiveOperation2() = 0 ;
} ;
// class ConcreteClass
class ConcreteClass : public AbstractClass
{
public:
virtual void PrimitiveOperation1() {}
virtual void PrimitiveOperation2() {}
} ;
}
客户端代码:
{
using namespace DesignPattern_TemplateMethod ;
AbstractClass *pobj = new ConcreteClass() ;
pobj->TemplateMethod() ;
}
访问者
一、功能
表示一个作用于某对象结构中的各元素的操作。它可以在不改变类层次的前提下,向已经存在的类层次中增加新的操作。
二、示例代码
namespace DesignPattern_Visitor
{
class ConcreteElementA ;
class ConcreteElementB ;
// class Visitor
class Visitor
{
public:
virtual void VisitorConcreteElementA(ConcreteElementA*) = 0 ;
virtual void VisitorConcreteElementB(ConcreteElementB*) = 0 ;
} ;
// class ConcreteVisitor1
class ConcreteVisitor1 : public Visitor
{
public:
virtual void VisitorConcreteElementA(ConcreteElementA* pA) { /*...*/ }
virtual void VisitorConcreteElementB(ConcreteElementB* pB) { /*...*/ }
} ;
// class ConcreteVisitor2
class ConcreteVisitor2 : public Visitor
{
public:
virtual void VisitorConcreteElementA(ConcreteElementA* pA) { /*...*/ }
virtual void VisitorConcreteElementB(ConcreteElementB* pB) { /*...*/ }
} ;
// class Element
class Element
{
public:
virtual void Accept(Visitor&) = 0 ;
} ;
// class ConcreteElementA
class ConcreteElementA : public Element
{
public:
virtual void Accept(Visitor& v) { v.VisitorConcreteElementA(this) ; }
} ;
// class ConcreteElementB
class ConcreteElementB : public Element
{
public:
virtual void Accept(Visitor& v) { v.VisitorConcreteElementB(this) ; }
} ;
}
客户端代码:
{
using namespace DesignPattern_Visitor ;
ConcreteVisitor1 v1 ; //算法1
ConcreteVisitor2 v2 ; //算法2
ConcreteElementA e1 ; //对象1
ConcreteElementB e2 ; //对象2
e1.Accept(v1) ; //对象1 + 算法1
e1.Accept(v2) ; //对象1 + 算法2
e2.Accept(v1) ; //对象2 + 算法1
e2.Accept(v2) ; //对象2 + 算法2
}
领悟设计模式--Template Method / Visitor
本文根据发表在CUJ Expert Forum上的两篇文章编译而成。C/C++ User's Journal是目前最出色的C/C++语言专业杂志,特别是在C++ Report闭刊之后,CUJ的地位更加突出。CUJ Expert Forum是CUJ主办的网上技术专栏,汇集2000年10月以来C++社群中顶尖专家的技术短文,并免费公开发布,精彩纷呈,是每一个C/C++学习者不可错过的资料。由Jim Hyslop和Herb Sutter主持的Conversation系列,是CUJ Expert Forum每期必备的精品专栏,以风趣幽默的对话形式讲解C++高级技术,在C++社群内得到广泛赞誉。译者特别挑选两篇设计模式方面的文章,介绍给大家。设计模式方面的经典著作是GoF的Design Patterns。但是那本书有一个缺点,不好懂。从风格上讲,该书与其说是为学习者而写作的教程范本,还不如说是给学术界人士看的学术报告,严谨有余,生动不足。这一点包括该书作者和象Bjarne Stroustrup这样的大师都从不讳言。实际上Design Pattern并非一定是晦涩难懂的,通过生动的例子,一个中等水平的C++学习者完全可以掌握基本用法,在自己的编程实践中使用,得到立竿见影的功效。这两篇文章就是很好的例证。本文翻译在保证技术完整性的前提下作了不少删节和修改,以便使文章显得更紧凑。
----------------------------------------------------------
人物介绍:
我 --- 一个追求上进的C++程序员,尚在试用期,聪明但是经验不足。
Wendy --- 公司里的技术大拿,就坐在我旁边的隔间里,C++大虾,最了不起的是,她是个女的!她什么都好,就是有点刻薄,
我对她真是又崇拜又嫉妒。
----------------------------------------------------------
I. Virtually Yours -- Template Method模式
我在研究Wendy写的一个类。那是她为这个项目写的一个抽象基类,而我的工作就是从中派生出一个具象类(concrete class)。这个类的public部分是这样的:
class Mountie {
public:
void read( std::istream & );
void write( std::ostream & ) const;
virtual ~Mountie();
很正常,virtual destructor表明这个类打算被继承。那么再看看其protected部分:
protected:
virtual void do_read( std::istream & );
virtual void do_write( std::ostream & ) const;
也不过就是一会儿的功夫,我识破了Wendy的把戏:她在使用template method模式。public成员函数read和write是非虚拟的,它们肯定是调用protected部分do_read/do_write虚拟成员函数来完成实际的工作。啊,我简直为自己的进步而飘飘然了!哈,Wendy,这回你可难不住我,还有什么招数?尽管放马过来... 突然,笑容在我脸上凝固,因为我看到了其private部分:
private:
virtual std::string classID() const = 0;
这是什么?一个private纯序函数,能工作么?我站了起来,
“Wendy,你的Mountie类好像不能工作耶,它有一个private virtual function。”
“你试过了?”她连头都不抬。
“嗯,那倒是没有啦,可是想想也不行啊?我的派生类怎么能override你的private函数呢?” 我嘟囔着。
“嗬,你倒是很确定啊!”Wendy的声音很轻柔,“你怎么老是这也不行,那也不行的,这几个月跟着我你就没学到什么东西吗?小菜鸟。”
真是可恶啊...
“小菜鸟,你全都忘了,访问控制级别跟一个函数是不是虚拟的根本没关系。判断一个函数是动态绑定还是静态绑定是函数调用解析的最后一个步骤。好好读读标准的3.4和5.2.2节吧。”
我完全处于下风,只好采取干扰战术。“好吧,就算你说的不错,我也还是不明白,何必把它设为private?”
“我且问你,倘若你不想让一个类中的成员函数被其他的类调用,应当如何处理?”
“当然是把它设置为private的,” 我回答道。
“那么你去看看我的Mountie类实现,特别是write()函数的实现。”
我正巴不得逃开Wendy那刺人的目光,便转过头去在我的屏幕上搜索,很快,我找到了:
void Mountie::write(std::ostream &Dudley) const
{
Dudley << classID() << std::endl;
do_write(Dudley);
}
嗨,最近卡通片真是看得太多了,居然犯这样的低级失误。还是老是承认吧:“好了,我明白了。classID()是一个实现细节,用来在保存对象时指示具象类的类型,派生类必须覆盖它,所以必须是纯虚的。但是既然是实现细节,就应该设为private的。”
“这还差不多,小菜鸟。”大虾点了点头,“现在给我解释一下为什么do_read()和do_write()是protected的?”
这个问题并不难,我组织了一下就回答:“因为派生类对象需要调用这两个函数的实现来读写其中的基类对象。”
“很好很好,”大虾差不多满意了,“不过,你再解释解释为什么我不把它们设为public的?”
现在我感觉好多了:“因为调用它们的时候必须以一种特定的方式进行。比如do_write()函数,必须先把类型信息写入,再把对象信息写入,这样读取的时候,负责生成对象的模块首先能够知道要读出来的对象是什么类型的,然后才能正确地从流中读取对象信息。”
“聪明啊,我的小菜鸟!”Wendy停顿了一下,“就跟学习外国口语一样,学习C++也不光是掌握语法而已,还必须要掌握大量的惯用法。”
“是啊是啊,我正打算读Coplien的书...”
[译者注:就是James Coplien 1992年的经典著作Advanced C++ Programming Style and Idioms]
大虾挥了挥她的手,“冷静,小菜鸟,我不是指先知Coplien的那本书,我是指某种结构背后隐含的惯用法。比如一个类有virtual destructor,相当于告诉你说:‘嗨,我是一个多态基类,来继承我吧!’ 而如果一个类的destructor不是虚拟的,则相当于是在说:‘我不能作为多态基类,看在老天的份上,别继承我。’”
“同样的,virtual函数的访问控制级别也具有隐含的意义。一个protected virtual function告诉你:‘你写的派生类应该,哦,可是说是必须调用我的实现。’而一个private virtual function是在说:‘派生类可以覆盖,也可以不覆盖我,随你的便。但是你不可以调用我的实现。’”
我点点头,告诉她我懂了,然后追问道:“那么public virtual function呢?”
“尽可能不要使用public virtual function。”她拿起一支笔写下了以下代码:
class HardToExtend
{
public:
virtual void f();
};
void HardToExtend::f()
{
// Perform a specific action
}
“假设你发布了这个类。在写第二版时,需求有所变化,你必须改用Template Method。可是这根本不可能,你知道为什么?”
“呃,这个...,不知道。”
“由两种可能的办法。其一,将f()的实现代码转移到一个新的函数中,然后将f()本身设为non-virtual的:
class HardToExtend
{
// possibly protected
virtual void do_f();
public:
void f();
};
void HardToExtend::f()
{
// pre-processing
do_f();
// post-processing
}
void HardToExtend::do_f()
{
// Perform a specific action
}
然而你原来写的派生类都是企图override函数f()而不是do_f()的,你必须改变所有的派生类实现,只要你错过了一个类,你的类层次就会染上先知Meyers所说的‘精神分裂的行径’。” [译者注:参见Scott Meyers,Effective C++, Item 37,绝对不要重新定义继承而来的非虚拟函数]
“另一种办法是将f()移到private区域,引入一个新的non-virtual函数:”
class HardToExtend
{
// possibly protected
virtual void f();
public:
void call_f();
};
“这会导致无数令人头痛的问题。首先,所有的客户都企图调用f()而不是call_f(),现在它们的代码都不能编译了。更有甚者,大部分派生类都回把f()放在public区域中,这样直接使用派生类的用户可以访问到你本来想保护的细节。”
“对待虚函数要象对待数据成员一样,把它们设为private的,直到设计上要求使用更宽松的访问控制再来调整。要知道由private入public易,由public入private难啊!”
[译者注:这篇文章所表达的思想具有一定的颠覆性,因为我们太容易在基类中设置public virtual function了,Java中甚至专门为这种做法建立了interface机制,现在竟然说这不好!一时间真是接受不了。但是仔细体会作者的意思,他并不是一般地反对public virtual function,只是在template method大背景下给出上述原则。虽然这个原则在一般的设计中也是值得考虑的,但是主要的应用领域还是在template method模式中。当然,template method是一种非常有用和常用的模式,因此也决定了本文提出的原则具有广泛的意义。]
----------------------------------------------------------------
II. Visitor模式
我正在为一个设计问题苦恼。试用期快结束了,我希望自己解决这个问题,来证明自己的进步。每个人都记得自己的第一份工作吧,也都应该知道在这个时候把活儿做好是多么的重要!我亲眼看到其他的新雇员没有过完试用期就被炒了鱿鱼,就是因为他们不懂得如何对付那个大虾...,别误会,我不是说她不好,她是我见过最棒的程序员,可就是有点刻薄古怪...。现在我拜她为师,不为别的,就是因为我十分希望能达到她那个高度。
我想在一个类层次(class hierarchy)中增加一个新的虚函数,但是这个类层次是由另外一帮人维护的,其他人碰都不能碰:
class Personnel
{
public:
virtual void Pay ( /*...*/ ) = 0;
virtual void Promote( /*...*/ ) = 0;
virtual void Accept ( PersonnelV& ) = 0;
// ... other functions ...
};
class Officer : public Personnel { /* override virtuals */ };
class Captain : public Officer { /* override virtuals */ };
class First : public Officer { /* override virtuals */ };
我想要一个函数,如果对象是船长(Captain)就这么做,如果是大副(First Officer)就那么做。Virtual function正是解决之道,在Personnel或者Officer中声明它,而在Captain和First覆盖(override)它。
糟糕的是,我不能增加这么一个虚函数。我知道可以用RTTI给出一个解决方案:
void f( Officer &o )
{
if( dynamic_cast<Captain*>(&o) )
/* do one thing */
else if( dynamic_cast<First*>(&o) )
/* do another thing */
}
int main()
{
Captain k;
First s;
f( k );
f( s );
}
但是我知道使用RTTI是公司编码标准所排斥的行为,我对自己说:“是的,虽然我以前不喜欢RTTI,但是这回我得改变对它的看法了。很显然,除了使用RTTI,别无它法。”
“任何问题都可以通过增加间接层次的方法解决。”
我噌地一下跳起来,那是大虾的声音,她不知道什么时候跑到我背后,“啊哟,您吓了我一跳...您刚才说什么?”
“任何问...”
“是的,我听清楚了,”我也不知道哪来的勇气,居然敢打断她,“我只是不知道您从哪冒出来的。”其实这话只不过是掩饰我内心的慌张。
“哈,算了吧,小菜鸟,”大虾斜着眼看着我,“你以为我不知道你心里想什么!”她把声音提高了八度,直盯着我,“那些可怜的C语言门徒才会使用switch语句处理不同的对象类型。你看:”
/* A not-atypical C program */
void f(struct someStruct *s)
{
switch(s->type) {
case APPLE:
/* do one thing */
break;
case ORANGE:
/* do another thing */
break;
/* ... etc. ... */
}
}
“这些人学习Stroustrup教主的C++语言时,最重要的事情就是学习如何设计好的类层次。”
“没错,”我又一次打断她,迫不及待地想让Wendy明白,我还是有两下子的,“他们应该设计一个Fruit基类,派生出Apple和Orange,用virtual function来作具体的事情。
“很好,小菜鸟。C语言门徒通常老习惯改不掉。但是,你应该知道,通过使用virtual function,你增加了一个间接层次。”她放下笔,“你所需要的不就是一个新的虚函数吗?”
“是的。可是我没有权力这么干。”
“因为你无权修改类层次,对吧!”
“您终于了解了情况,我们没法动它。也不知道这个该死的类层次是哪个家伙设计的...” 我嘀嘀咕咕着。
“是我设计的。”
“啊...,真的?!这个,嘿嘿...”,我极为尴尬。
“这个类层次必须非常稳定,因为有跨平台的问题。但是它的设计允许你增加新的virtual function,而不必烦劳RTTI。你可以通过增加一个间接层次的办法解决这个问题。请问,Personnel::Accept是什么?”
”嗯,这个...”
“这个类实现了一个模式,可惜这个模式的名字起得不太好,是个PNP,叫Visitor模式。”
[译者注:PNP,Poor-Named Pattern, 没起好名字的模式]
“啊,我刚刚读过Visitor模式。但是那只不过是允许若干对象之间相互迭代访问的模式,不是吗?”
她叹了一口气,“这是流行的错误理解。那个V,我觉得毋宁说是Visitor,还不如说是Virtual更好。这个PNP最重要的用途是允许在不改变类层次的前提下,向已经存在的类层次中增加新的虚函数。首先来看看Personnel及其派生类的Accept实现细节。”她拿起笔写下:
void Personnel::Accept( PersonnelV& v )
{ v.Visit( *this ); }
void Officer::Accept ( PersonnelV& v )
{ v.Visit( *this ); }
void Captain::Accept ( PersonnelV& v )
{ v.Visit( *this ); }
void First::Accept ( PersonnelV& v )
{ v.Visit( *this ); }
“Visitor的基类如下:”
class PersonnelV/*isitor*/
{
public:
virtual void Visit( Personnel& ) = 0;
virtual void Visit( Officer& ) = 0;
virtual void Visit( Captain& ) = 0;
virtual void Visit( First& ) = 0;
};
“啊,我记起来了。当我要利用Personnel类层次的多态性时,我只要调用Personnel::Accept(myVisitorObject)。由于Accept是虚函数,我的myVisitorObject.Visit()会针对正确的对象类型调用,根据重载法则,编译器会挑选最贴切的那个Visit来调用。这不相当于增加了一个新的虚拟函数了吗?”
“没错,小菜鸟。只要类层次支持Accept,我们就可以在不改动类层次的情况下增加新的虚函数了。”
“好了,我现在知道该怎么办了”,我写道:
class DoSomething : public PersonnelV
{
public:
virtual void Visit( Personnel& );
virtual void Visit( Officer& );
virtual void Visit( Captain& );
virtual void Visit( First& );
};
void DoSomething::Visit( Captain& c )
{
if( femaleGuestStarIsPresent )
c.TurnOnCharm();
else
c.StartFight();
}
void DoSomething::Visit( First& f )
{
f.RaiseEyebrowAtCaptainsBehavior();
}
void f( Personnel& p )
{
p.Accept( DoSomething() ); // 相当于 p.DoSomething()
}
int main()
{
Captain k;
First s;
f( k );
f( s );
}
大虾满意地笑了,“也许这个模式换一个名字会更好理解,可惜世事往往不遂人意...”。
相关模式的比较
(1)Builder/Abstract Factory
Builder模式着重于一步步构造一个复杂对象,而Abstract Factory着重于多个系列产品对象。Builder的过程具有明显的组装的性质,它所构造的对象往往比较复杂,是几个子部分的整合。
Builder在最后一步返回产品,而Abstract Factory是立即返回产品,它通常只是一个'new'操作。
Builder构造的对象可能没有抽象类,而Abstract Factory构造的对象通常都有抽象类,通常返回的都是抽象类的指针。有没有抽象类是由具体情况决定的,如果不同的方法build出的对象差别太大,就不需要有抽象类,这时每个具体的builder类需要提供各自的Get函数,以返回构造的对象,就如下例中的GetASCIIText()、GetTeXText()和GetTextWidget()。
(要解释为什么Builder构造的对象可能差别很大导致不能提取抽象类,需要仔细品味前面所说的Builder模式本来的动机--“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示”)
(2)Builder/Bridge
Builder模式抛开它的"创建"因素不谈,实际上它也是把抽象接口与实现相分开的模式,与Bridge模式类似。Director::Construct就是用户看到的接口,ConcreteBuilder就是接口具体的实现方法。只不过这里的实现都是有"创建"作用的。
(3)Abstract Factory/Factory Method
没有本质的区别。Abstract Factory中类厂只是一组Create函数,每个Create都是一个factory method。
(4)Prototype/Abstract Factory/Builder/Factory Method
Prototype不需要通过中间类(如类厂)来创建对象,减少了类个数。
Prototype是复制一个已有的对象实例,而其他模式是创建一个新的实例。
(5)Adapter/Bridge
Adapter在类已经设计后实施,当有两个不兼容的类需要同时工作时,就可能需要应用Adapter模式。而Bridge在设计类之前实施,设计者必须事先知道,是否一个抽象将有多个实现,是否抽象和实现两者是独立演化的。
(6)Facade/Adapter
Facade定义一个新的接口,而Adapter复用一个原有的接口。
(7)Composite/Decorator
Decorator在实现上采用了Composite模式,但它们本质上是不同的。Composite主要为了使多个相关对象能够以统一的方式处理,是否是组合对象并不重要。 Decorator主要为了能够在运行时动态地为对象增加功能,而不用通过生成子类的方法。 Composite重在表示,而Decorator重在修饰。
(8)Proxy/Decorator
Proxy与Decorator在实现上很类似,都保留了另一个对象的指针,向其发送请求。
Proxy不仅仅是为了给对象增加功能,而Decorator的目的只是为了给对象增加功能。如果Decorator做了“增加功能”以外的事,那它实际上也“局部”演化成了Proxy。
Proxy不能动态增加职责,它描述的是静态的关系。如果Proxy也能动态地给对象增加职责,那他实际上也演化成了Decorator。
(9)Interpreter/Composite
在我看来,Interpreter模式仅仅是Composite模式在语言解释领域内的一个应用实例,因为文法一般都是递归的。
(10)Mediator/Facade
已有类结构图如下:
类D、E、F、G、H构成一子系统,类A、B、C是三个client类。A通过连线1、2与D、E相关联,B通过连线3、4与D、H相关联,C通过连线5、6与D、G相关联。为方便A、B、C与子系统的联系,加入facade类:
加入Facade类后,类ABC将不再与子系统内的类直接关联,而子系统内部的联系也并未改变。加上连线后,如图:
类A、B、C只与Facade相联,为完成相应的功能,Facade将分别与DEHG相连。从上可看出Facade模式的一个基本特点:Facade只是为子系统提供一个抽象接口,简化client对子系统的调用;它并未定义新的功能,不影响子系统已有的内部关联。Facade可能只对client可见,而不需对子系统可见。
Mediator模式是作用于上述图中的子系统内部,目的是为了减少子系统内部复杂的关联。对其应用Mediator模式后,结构图如下:
Mediator的特点在于:Mediator是子系统中各类之间相互联系的核心枢扭。各类的接口中只关注于定义自身的功能属性,不将它与其它类的联系代码包含其中,而将其包含到一个中间类Mediator中。实际上,一个子系统的功能是通过各类间的相互作用,即Mediator类,表现出来的。当要改变一个子系统的功能时,只需定义新的Mediator类,而不需改变各类的定义。
(11)Strategy/Bridge
我没看出二者有任何本质的区别?!
(12)Template Method/Strategy
Template Method通过继承把易变算法分离,而Strategy通过创建新类把易变算法隔离。Strategy的优点在于易切换算法,因为算法可以用一个单独的对象表示。而Template method则不方便,如果想切换算法,必须生成一个新的对象,一般新对象与原有对象的大部分数据都是相同的,这意味着有赋值操作或拷贝构造操作。
一、功能
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
二、示例代码
namespace DesignPattern_Command
{
// class Receiver
class Receiver
{
public:
void SomeOperation() {}
} ;
// class Command
class Command
{
public:
Command() : _r(NULL) {}
virtual void Execute() = 0 ;
void SetReceiver(Receiver* pr) { _r = pr ; }
protected:
Receiver* _r ;
} ;
// class ConcreteCommand
class ConcreteCommand : public Command
{
public:
virtual void Execute()
{
// do some operations
(_r) ? _r->SomeOperation() : 0 ; // receiver do some operations
}
} ;
// class Invoker
class Invoker
{
public:
void AddCommand(Command* pc)
{
assert(pc) ;
_list.push_back(pc) ;
}
void ExecuteCommand()
{
Command* pc = _list.back() ;
pc->Execute() ;
}
void UndoCommand() { /*...*/ }
void RedoCommand() { /*...*/ }
private:
vector< Command* > _list ;
} ;
}
客户端代码:
{
using namespace DesignPattern_Command ;
Receiver r ;
ConcreteCommand c ;
c.SetReceiver(&r) ;
Invoker inv ;
inv.AddCommand(&c) ;
inv.ExecuteCommand() ;
}
中介者
一、功能
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
二、示例代码
namespace DesignPattern_Mediator
{
class Mediator ;
// class Subsystem_ClassA
class Subsystem_ClassA
{
public:
Subsystem_ClassA(Mediator *pMediator) : _mediator(pMediator) {}
void Operation1() ;
void Operation2() { /*...*/ }
private:
Mediator *_mediator ;
} ;
// class Subsystem_ClassB
class Subsystem_ClassB
{
public:
Subsystem_ClassB(Mediator *pMediator) : _mediator(pMediator) {}
void Operation1() ;
void Operation2() { /*...*/ }
private:
Mediator *_mediator ;
} ;
// class Medaitor
class Mediator
{
public:
Mediator() ;
void Proc1() ;
void Proc2() ;
void Test1() { _obja->Operation1() ; }
void Test2() { _objb->Operation1() ; }
private:
Subsystem_ClassA *_obja ;
Subsystem_ClassB *_objb ;
} ;
void Subsystem_ClassA::Operation1()
{
// ...
_mediator->Proc1() ;
// ...
}
void Subsystem_ClassB::Operation1()
{
// ...
_mediator->Proc2() ;
// ...
}
Mediator::Mediator()
{
_obja = new Subsystem_ClassA(this) ;
_objb = new Subsystem_ClassB(this) ;
}
void Mediator::Proc1()
{
// ...
_objb->Operation2() ;
// ...
}
void Mediator::Proc2()
{
// ...
_obja->Operation2() ;
// ...
}
}
客户端代码:
{
using namespace DesignPattern_Mediator ;
Mediator m ;
m.Test1() ;
m.Test2() ;
}
上例中的Mediator封装了Subsystem_ClassA::Operation1()-->Subsystem_ClassB::Operation2()和Subsystem_ClassB::Operation1()-->Subsystem_ClassA::Operation2()的逻辑,而Subsystem_ClassA和Subsystem_ClassB彼此是不可见的。如果未来这种逻辑改变了,Subsystem_ClassA和Subsystem_ClassB都无需改变,只要改Mediator就可以了。
备忘录
一、功能
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
二、示例代码
namespace DesignPattern_Memento
{
class Originator ;
// class Memento
class Memento
{
public:
void GetState(Originator*) { /*...*/ }
void SetState(Originator*) { /*...*/ }
} ;
// class Originator
class Originator
{
public:
Memento* CreateMemento()
{
Memento* p = new Memento() ;
p->SetState(this) ;
return p ;
}
void SetMemento(Memento* p)
{
assert(p) ;
p->GetState(this) ;
}
} ;
}
客户端代码:
{
using namespace DesignPattern_Memento ;
Originator obj ;
Memento *pMem = obj.CreateMemento() ; //保存状态
// ...
obj.SetMemento(pMem) ; //恢复状态
}
观察者
一、功能
定义对象间的一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
二、示例代码
namespace DesignPattern_Observer
{
class Subject ;
// class Observer
class Observer
{
public:
virtual void Update() = 0 ;
void SetSubject(Subject *pSubject) { _subject = pSubject ; }
protected:
Subject *_subject ;
} ;
// class ConcreteObserver
class ConcreteObserver : public Observer
{
public:
virtual void Update() { /* using '_subject' to get data */ }
} ;
// class Subject
class Subject
{
public:
void AddSubject(Observer* pObserver)
{
_list.push_back(pObserver) ;
pObserver->SetSubject(this) ;
}
void Notify()
{
vector< Observer* >::const_iterator it ;
for (it = _list.begin(); it != _list.end(); it++)
(*it)->Update() ;
}
// ...
private:
vector< Observer* > _list ;
} ;
// class ConcreteSubject
class ConcreteSubject : public Subject
{
// ...
} ;
}
客户端代码:
{
using namespace DesignPattern_Observer ;
ConcreteObserver o1 ;
ConcreteObserver o2 ;
ConcreteSubject s ;
s.AddSubject(&o1) ;
s.AddSubject(&o2) ;
s.Notify() ;
}
状态
一、功能
把一个对象的内部状态从对象中分离出来,形成单独的状态对象,所有与该状态相关的行为都放入该状态对象中。
二、示例代码
namespace DesignPattern_State
{
class State ;
// class Context
class Context
{
public:
Context(State *pState = NULL) : _state(pState) {}
void Request1() ;
void Request2() ;
void ChangeState(State* newstate) { assert(newstate) ; _state = newstate ; }
private:
State *_state ;
} ;
// class State
class State
{
public:
virtual void Handle1(Context*) {}
virtual void Handle2(Context*) {}
protected:
State() {}
void ChangeState(Context* c, State* s) { assert(c) ; c->ChangeState(s) ; }
} ;
void Context::Request1() { assert(_state) ; _state->Handle1(this) ; }
void Context::Request2() { assert(_state) ; _state->Handle2(this) ; }
// class ConcreteStateA
class ConcreteStateA : public State
{
public:
static State* Instance() { return &_state ; } //singleton
virtual void Handle1(Context*) ;
virtual void Handle2(Context*) ;
protected:
ConcreteStateA() {}
private:
static ConcreteStateA _state ;
ConcreteStateA(const ConcreteStateA&) ;
} ;
ConcreteStateA ConcreteStateA::_state ;
// class ConcreteStateB
class ConcreteStateB : public State
{
public:
static State* Instance() { return &_state ; } //singleton
virtual void Handle1(Context*) ;
virtual void Handle2(Context*) ;
protected:
ConcreteStateB() {}
private:
static ConcreteStateB _state ;
ConcreteStateB(const ConcreteStateB&) ;
} ;
ConcreteStateB ConcreteStateB::_state ;
void ConcreteStateA::Handle1(Context* c)
{
// do something with using 'c'
ChangeState(c, ConcreteStateB::Instance()) ;
}
void ConcreteStateA::Handle2(Context* c)
{
// do something with using 'c'
ChangeState(c, ConcreteStateB::Instance()) ;
}
void ConcreteStateB::Handle1(Context* c)
{
// do something with using 'c'
}
void ConcreteStateB::Handle2(Context* c)
{
// do something with using 'c'
}
}
客户端代码:
{
using namespace DesignPattern_State ;
Context context ;
context.ChangeState(ConcreteStateA::Instance()) ; //状态1
context.Request1() ; //操作1
context.ChangeState(ConcreteStateB::Instance()) ; //状态2
context.Request2() ; //操作2
}
策略
一、功能
定义一系列的算法,把它们分别封装起来,并使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
二、示例代码
namespace DesignPattern_Strategy
{
// class Strategy
class Strategy
{
public:
virtual void Algorithm() = 0 ;
} ;
// class ConcreteStrategyA
class ConcreteStrategyA : public Strategy
{
public:
virtual void Algorithm() { /*...*/ }
} ;
// class ConcreteStrategyB
class ConcreteStrategyB : public Strategy
{
public:
virtual void Algorithm() { /*...*/ }
} ;
// class ConcreteStrategyC
class ConcreteStrategyC : public Strategy
{
public:
virtual void Algorithm() { /*...*/ }
} ;
// class Context
class Context
{
public:
void SetStrategy(Strategy* pStrategy) { _strategy = pStrategy ; }
void Operation()
{
assert(_strategy) ;
// ...
_strategy->Algorithm() ;
// ...
}
private:
Strategy *_strategy ;
} ;
}
客户端代码:
{
using namespace DesignPattern_Strategy ;
Context c ;
ConcreteStrategyA s1 ;
c.SetStrategy(&s1) ;
c.Operation() ; //使用算法A
ConcreteStrategyB s2 ;
c.SetStrategy(&s2) ;
c.Operation() ; //使用算法B
ConcreteStrategyC s3 ;
c.SetStrategy(&s3) ;
c.Operation() ; //使用算法C
}
模板方法
一、功能
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
二、示例代码
namespace DesignPattern_TemplateMethod
{
// class AbstractClass
class AbstractClass
{
public:
void TemplateMethod()
{
// ...
PrimitiveOperation1() ;
// ...
PrimitiveOperation2() ;
}
virtual void PrimitiveOperation1() = 0 ;
virtual void PrimitiveOperation2() = 0 ;
} ;
// class ConcreteClass
class ConcreteClass : public AbstractClass
{
public:
virtual void PrimitiveOperation1() {}
virtual void PrimitiveOperation2() {}
} ;
}
客户端代码:
{
using namespace DesignPattern_TemplateMethod ;
AbstractClass *pobj = new ConcreteClass() ;
pobj->TemplateMethod() ;
}
访问者
一、功能
表示一个作用于某对象结构中的各元素的操作。它可以在不改变类层次的前提下,向已经存在的类层次中增加新的操作。
二、示例代码
namespace DesignPattern_Visitor
{
class ConcreteElementA ;
class ConcreteElementB ;
// class Visitor
class Visitor
{
public:
virtual void VisitorConcreteElementA(ConcreteElementA*) = 0 ;
virtual void VisitorConcreteElementB(ConcreteElementB*) = 0 ;
} ;
// class ConcreteVisitor1
class ConcreteVisitor1 : public Visitor
{
public:
virtual void VisitorConcreteElementA(ConcreteElementA* pA) { /*...*/ }
virtual void VisitorConcreteElementB(ConcreteElementB* pB) { /*...*/ }
} ;
// class ConcreteVisitor2
class ConcreteVisitor2 : public Visitor
{
public:
virtual void VisitorConcreteElementA(ConcreteElementA* pA) { /*...*/ }
virtual void VisitorConcreteElementB(ConcreteElementB* pB) { /*...*/ }
} ;
// class Element
class Element
{
public:
virtual void Accept(Visitor&) = 0 ;
} ;
// class ConcreteElementA
class ConcreteElementA : public Element
{
public:
virtual void Accept(Visitor& v) { v.VisitorConcreteElementA(this) ; }
} ;
// class ConcreteElementB
class ConcreteElementB : public Element
{
public:
virtual void Accept(Visitor& v) { v.VisitorConcreteElementB(this) ; }
} ;
}
客户端代码:
{
using namespace DesignPattern_Visitor ;
ConcreteVisitor1 v1 ; //算法1
ConcreteVisitor2 v2 ; //算法2
ConcreteElementA e1 ; //对象1
ConcreteElementB e2 ; //对象2
e1.Accept(v1) ; //对象1 + 算法1
e1.Accept(v2) ; //对象1 + 算法2
e2.Accept(v1) ; //对象2 + 算法1
e2.Accept(v2) ; //对象2 + 算法2
}
领悟设计模式--Template Method / Visitor
本文根据发表在CUJ Expert Forum上的两篇文章编译而成。C/C++ User's Journal是目前最出色的C/C++语言专业杂志,特别是在C++ Report闭刊之后,CUJ的地位更加突出。CUJ Expert Forum是CUJ主办的网上技术专栏,汇集2000年10月以来C++社群中顶尖专家的技术短文,并免费公开发布,精彩纷呈,是每一个C/C++学习者不可错过的资料。由Jim Hyslop和Herb Sutter主持的Conversation系列,是CUJ Expert Forum每期必备的精品专栏,以风趣幽默的对话形式讲解C++高级技术,在C++社群内得到广泛赞誉。译者特别挑选两篇设计模式方面的文章,介绍给大家。设计模式方面的经典著作是GoF的Design Patterns。但是那本书有一个缺点,不好懂。从风格上讲,该书与其说是为学习者而写作的教程范本,还不如说是给学术界人士看的学术报告,严谨有余,生动不足。这一点包括该书作者和象Bjarne Stroustrup这样的大师都从不讳言。实际上Design Pattern并非一定是晦涩难懂的,通过生动的例子,一个中等水平的C++学习者完全可以掌握基本用法,在自己的编程实践中使用,得到立竿见影的功效。这两篇文章就是很好的例证。本文翻译在保证技术完整性的前提下作了不少删节和修改,以便使文章显得更紧凑。
----------------------------------------------------------
人物介绍:
我 --- 一个追求上进的C++程序员,尚在试用期,聪明但是经验不足。
Wendy --- 公司里的技术大拿,就坐在我旁边的隔间里,C++大虾,最了不起的是,她是个女的!她什么都好,就是有点刻薄,
我对她真是又崇拜又嫉妒。
----------------------------------------------------------
I. Virtually Yours -- Template Method模式
我在研究Wendy写的一个类。那是她为这个项目写的一个抽象基类,而我的工作就是从中派生出一个具象类(concrete class)。这个类的public部分是这样的:
class Mountie {
public:
void read( std::istream & );
void write( std::ostream & ) const;
virtual ~Mountie();
很正常,virtual destructor表明这个类打算被继承。那么再看看其protected部分:
protected:
virtual void do_read( std::istream & );
virtual void do_write( std::ostream & ) const;
也不过就是一会儿的功夫,我识破了Wendy的把戏:她在使用template method模式。public成员函数read和write是非虚拟的,它们肯定是调用protected部分do_read/do_write虚拟成员函数来完成实际的工作。啊,我简直为自己的进步而飘飘然了!哈,Wendy,这回你可难不住我,还有什么招数?尽管放马过来... 突然,笑容在我脸上凝固,因为我看到了其private部分:
private:
virtual std::string classID() const = 0;
这是什么?一个private纯序函数,能工作么?我站了起来,
“Wendy,你的Mountie类好像不能工作耶,它有一个private virtual function。”
“你试过了?”她连头都不抬。
“嗯,那倒是没有啦,可是想想也不行啊?我的派生类怎么能override你的private函数呢?” 我嘟囔着。
“嗬,你倒是很确定啊!”Wendy的声音很轻柔,“你怎么老是这也不行,那也不行的,这几个月跟着我你就没学到什么东西吗?小菜鸟。”
真是可恶啊...
“小菜鸟,你全都忘了,访问控制级别跟一个函数是不是虚拟的根本没关系。判断一个函数是动态绑定还是静态绑定是函数调用解析的最后一个步骤。好好读读标准的3.4和5.2.2节吧。”
我完全处于下风,只好采取干扰战术。“好吧,就算你说的不错,我也还是不明白,何必把它设为private?”
“我且问你,倘若你不想让一个类中的成员函数被其他的类调用,应当如何处理?”
“当然是把它设置为private的,” 我回答道。
“那么你去看看我的Mountie类实现,特别是write()函数的实现。”
我正巴不得逃开Wendy那刺人的目光,便转过头去在我的屏幕上搜索,很快,我找到了:
void Mountie::write(std::ostream &Dudley) const
{
Dudley << classID() << std::endl;
do_write(Dudley);
}
嗨,最近卡通片真是看得太多了,居然犯这样的低级失误。还是老是承认吧:“好了,我明白了。classID()是一个实现细节,用来在保存对象时指示具象类的类型,派生类必须覆盖它,所以必须是纯虚的。但是既然是实现细节,就应该设为private的。”
“这还差不多,小菜鸟。”大虾点了点头,“现在给我解释一下为什么do_read()和do_write()是protected的?”
这个问题并不难,我组织了一下就回答:“因为派生类对象需要调用这两个函数的实现来读写其中的基类对象。”
“很好很好,”大虾差不多满意了,“不过,你再解释解释为什么我不把它们设为public的?”
现在我感觉好多了:“因为调用它们的时候必须以一种特定的方式进行。比如do_write()函数,必须先把类型信息写入,再把对象信息写入,这样读取的时候,负责生成对象的模块首先能够知道要读出来的对象是什么类型的,然后才能正确地从流中读取对象信息。”
“聪明啊,我的小菜鸟!”Wendy停顿了一下,“就跟学习外国口语一样,学习C++也不光是掌握语法而已,还必须要掌握大量的惯用法。”
“是啊是啊,我正打算读Coplien的书...”
[译者注:就是James Coplien 1992年的经典著作Advanced C++ Programming Style and Idioms]
大虾挥了挥她的手,“冷静,小菜鸟,我不是指先知Coplien的那本书,我是指某种结构背后隐含的惯用法。比如一个类有virtual destructor,相当于告诉你说:‘嗨,我是一个多态基类,来继承我吧!’ 而如果一个类的destructor不是虚拟的,则相当于是在说:‘我不能作为多态基类,看在老天的份上,别继承我。’”
“同样的,virtual函数的访问控制级别也具有隐含的意义。一个protected virtual function告诉你:‘你写的派生类应该,哦,可是说是必须调用我的实现。’而一个private virtual function是在说:‘派生类可以覆盖,也可以不覆盖我,随你的便。但是你不可以调用我的实现。’”
我点点头,告诉她我懂了,然后追问道:“那么public virtual function呢?”
“尽可能不要使用public virtual function。”她拿起一支笔写下了以下代码:
class HardToExtend
{
public:
virtual void f();
};
void HardToExtend::f()
{
// Perform a specific action
}
“假设你发布了这个类。在写第二版时,需求有所变化,你必须改用Template Method。可是这根本不可能,你知道为什么?”
“呃,这个...,不知道。”
“由两种可能的办法。其一,将f()的实现代码转移到一个新的函数中,然后将f()本身设为non-virtual的:
class HardToExtend
{
// possibly protected
virtual void do_f();
public:
void f();
};
void HardToExtend::f()
{
// pre-processing
do_f();
// post-processing
}
void HardToExtend::do_f()
{
// Perform a specific action
}
然而你原来写的派生类都是企图override函数f()而不是do_f()的,你必须改变所有的派生类实现,只要你错过了一个类,你的类层次就会染上先知Meyers所说的‘精神分裂的行径’。” [译者注:参见Scott Meyers,Effective C++, Item 37,绝对不要重新定义继承而来的非虚拟函数]
“另一种办法是将f()移到private区域,引入一个新的non-virtual函数:”
class HardToExtend
{
// possibly protected
virtual void f();
public:
void call_f();
};
“这会导致无数令人头痛的问题。首先,所有的客户都企图调用f()而不是call_f(),现在它们的代码都不能编译了。更有甚者,大部分派生类都回把f()放在public区域中,这样直接使用派生类的用户可以访问到你本来想保护的细节。”
“对待虚函数要象对待数据成员一样,把它们设为private的,直到设计上要求使用更宽松的访问控制再来调整。要知道由private入public易,由public入private难啊!”
[译者注:这篇文章所表达的思想具有一定的颠覆性,因为我们太容易在基类中设置public virtual function了,Java中甚至专门为这种做法建立了interface机制,现在竟然说这不好!一时间真是接受不了。但是仔细体会作者的意思,他并不是一般地反对public virtual function,只是在template method大背景下给出上述原则。虽然这个原则在一般的设计中也是值得考虑的,但是主要的应用领域还是在template method模式中。当然,template method是一种非常有用和常用的模式,因此也决定了本文提出的原则具有广泛的意义。]
----------------------------------------------------------------
II. Visitor模式
我正在为一个设计问题苦恼。试用期快结束了,我希望自己解决这个问题,来证明自己的进步。每个人都记得自己的第一份工作吧,也都应该知道在这个时候把活儿做好是多么的重要!我亲眼看到其他的新雇员没有过完试用期就被炒了鱿鱼,就是因为他们不懂得如何对付那个大虾...,别误会,我不是说她不好,她是我见过最棒的程序员,可就是有点刻薄古怪...。现在我拜她为师,不为别的,就是因为我十分希望能达到她那个高度。
我想在一个类层次(class hierarchy)中增加一个新的虚函数,但是这个类层次是由另外一帮人维护的,其他人碰都不能碰:
class Personnel
{
public:
virtual void Pay ( /*...*/ ) = 0;
virtual void Promote( /*...*/ ) = 0;
virtual void Accept ( PersonnelV& ) = 0;
// ... other functions ...
};
class Officer : public Personnel { /* override virtuals */ };
class Captain : public Officer { /* override virtuals */ };
class First : public Officer { /* override virtuals */ };
我想要一个函数,如果对象是船长(Captain)就这么做,如果是大副(First Officer)就那么做。Virtual function正是解决之道,在Personnel或者Officer中声明它,而在Captain和First覆盖(override)它。
糟糕的是,我不能增加这么一个虚函数。我知道可以用RTTI给出一个解决方案:
void f( Officer &o )
{
if( dynamic_cast<Captain*>(&o) )
/* do one thing */
else if( dynamic_cast<First*>(&o) )
/* do another thing */
}
int main()
{
Captain k;
First s;
f( k );
f( s );
}
但是我知道使用RTTI是公司编码标准所排斥的行为,我对自己说:“是的,虽然我以前不喜欢RTTI,但是这回我得改变对它的看法了。很显然,除了使用RTTI,别无它法。”
“任何问题都可以通过增加间接层次的方法解决。”
我噌地一下跳起来,那是大虾的声音,她不知道什么时候跑到我背后,“啊哟,您吓了我一跳...您刚才说什么?”
“任何问...”
“是的,我听清楚了,”我也不知道哪来的勇气,居然敢打断她,“我只是不知道您从哪冒出来的。”其实这话只不过是掩饰我内心的慌张。
“哈,算了吧,小菜鸟,”大虾斜着眼看着我,“你以为我不知道你心里想什么!”她把声音提高了八度,直盯着我,“那些可怜的C语言门徒才会使用switch语句处理不同的对象类型。你看:”
/* A not-atypical C program */
void f(struct someStruct *s)
{
switch(s->type) {
case APPLE:
/* do one thing */
break;
case ORANGE:
/* do another thing */
break;
/* ... etc. ... */
}
}
“这些人学习Stroustrup教主的C++语言时,最重要的事情就是学习如何设计好的类层次。”
“没错,”我又一次打断她,迫不及待地想让Wendy明白,我还是有两下子的,“他们应该设计一个Fruit基类,派生出Apple和Orange,用virtual function来作具体的事情。
“很好,小菜鸟。C语言门徒通常老习惯改不掉。但是,你应该知道,通过使用virtual function,你增加了一个间接层次。”她放下笔,“你所需要的不就是一个新的虚函数吗?”
“是的。可是我没有权力这么干。”
“因为你无权修改类层次,对吧!”
“您终于了解了情况,我们没法动它。也不知道这个该死的类层次是哪个家伙设计的...” 我嘀嘀咕咕着。
“是我设计的。”
“啊...,真的?!这个,嘿嘿...”,我极为尴尬。
“这个类层次必须非常稳定,因为有跨平台的问题。但是它的设计允许你增加新的virtual function,而不必烦劳RTTI。你可以通过增加一个间接层次的办法解决这个问题。请问,Personnel::Accept是什么?”
”嗯,这个...”
“这个类实现了一个模式,可惜这个模式的名字起得不太好,是个PNP,叫Visitor模式。”
[译者注:PNP,Poor-Named Pattern, 没起好名字的模式]
“啊,我刚刚读过Visitor模式。但是那只不过是允许若干对象之间相互迭代访问的模式,不是吗?”
她叹了一口气,“这是流行的错误理解。那个V,我觉得毋宁说是Visitor,还不如说是Virtual更好。这个PNP最重要的用途是允许在不改变类层次的前提下,向已经存在的类层次中增加新的虚函数。首先来看看Personnel及其派生类的Accept实现细节。”她拿起笔写下:
void Personnel::Accept( PersonnelV& v )
{ v.Visit( *this ); }
void Officer::Accept ( PersonnelV& v )
{ v.Visit( *this ); }
void Captain::Accept ( PersonnelV& v )
{ v.Visit( *this ); }
void First::Accept ( PersonnelV& v )
{ v.Visit( *this ); }
“Visitor的基类如下:”
class PersonnelV/*isitor*/
{
public:
virtual void Visit( Personnel& ) = 0;
virtual void Visit( Officer& ) = 0;
virtual void Visit( Captain& ) = 0;
virtual void Visit( First& ) = 0;
};
“啊,我记起来了。当我要利用Personnel类层次的多态性时,我只要调用Personnel::Accept(myVisitorObject)。由于Accept是虚函数,我的myVisitorObject.Visit()会针对正确的对象类型调用,根据重载法则,编译器会挑选最贴切的那个Visit来调用。这不相当于增加了一个新的虚拟函数了吗?”
“没错,小菜鸟。只要类层次支持Accept,我们就可以在不改动类层次的情况下增加新的虚函数了。”
“好了,我现在知道该怎么办了”,我写道:
class DoSomething : public PersonnelV
{
public:
virtual void Visit( Personnel& );
virtual void Visit( Officer& );
virtual void Visit( Captain& );
virtual void Visit( First& );
};
void DoSomething::Visit( Captain& c )
{
if( femaleGuestStarIsPresent )
c.TurnOnCharm();
else
c.StartFight();
}
void DoSomething::Visit( First& f )
{
f.RaiseEyebrowAtCaptainsBehavior();
}
void f( Personnel& p )
{
p.Accept( DoSomething() ); // 相当于 p.DoSomething()
}
int main()
{
Captain k;
First s;
f( k );
f( s );
}
大虾满意地笑了,“也许这个模式换一个名字会更好理解,可惜世事往往不遂人意...”。
相关模式的比较
(1)Builder/Abstract Factory
Builder模式着重于一步步构造一个复杂对象,而Abstract Factory着重于多个系列产品对象。Builder的过程具有明显的组装的性质,它所构造的对象往往比较复杂,是几个子部分的整合。
Builder在最后一步返回产品,而Abstract Factory是立即返回产品,它通常只是一个'new'操作。
Builder构造的对象可能没有抽象类,而Abstract Factory构造的对象通常都有抽象类,通常返回的都是抽象类的指针。有没有抽象类是由具体情况决定的,如果不同的方法build出的对象差别太大,就不需要有抽象类,这时每个具体的builder类需要提供各自的Get函数,以返回构造的对象,就如下例中的GetASCIIText()、GetTeXText()和GetTextWidget()。
(要解释为什么Builder构造的对象可能差别很大导致不能提取抽象类,需要仔细品味前面所说的Builder模式本来的动机--“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示”)
(2)Builder/Bridge
Builder模式抛开它的"创建"因素不谈,实际上它也是把抽象接口与实现相分开的模式,与Bridge模式类似。Director::Construct就是用户看到的接口,ConcreteBuilder就是接口具体的实现方法。只不过这里的实现都是有"创建"作用的。
(3)Abstract Factory/Factory Method
没有本质的区别。Abstract Factory中类厂只是一组Create函数,每个Create都是一个factory method。
(4)Prototype/Abstract Factory/Builder/Factory Method
Prototype不需要通过中间类(如类厂)来创建对象,减少了类个数。
Prototype是复制一个已有的对象实例,而其他模式是创建一个新的实例。
(5)Adapter/Bridge
Adapter在类已经设计后实施,当有两个不兼容的类需要同时工作时,就可能需要应用Adapter模式。而Bridge在设计类之前实施,设计者必须事先知道,是否一个抽象将有多个实现,是否抽象和实现两者是独立演化的。
(6)Facade/Adapter
Facade定义一个新的接口,而Adapter复用一个原有的接口。
(7)Composite/Decorator
Decorator在实现上采用了Composite模式,但它们本质上是不同的。Composite主要为了使多个相关对象能够以统一的方式处理,是否是组合对象并不重要。 Decorator主要为了能够在运行时动态地为对象增加功能,而不用通过生成子类的方法。 Composite重在表示,而Decorator重在修饰。
(8)Proxy/Decorator
Proxy与Decorator在实现上很类似,都保留了另一个对象的指针,向其发送请求。
Proxy不仅仅是为了给对象增加功能,而Decorator的目的只是为了给对象增加功能。如果Decorator做了“增加功能”以外的事,那它实际上也“局部”演化成了Proxy。
Proxy不能动态增加职责,它描述的是静态的关系。如果Proxy也能动态地给对象增加职责,那他实际上也演化成了Decorator。
(9)Interpreter/Composite
在我看来,Interpreter模式仅仅是Composite模式在语言解释领域内的一个应用实例,因为文法一般都是递归的。
(10)Mediator/Facade
已有类结构图如下:
类D、E、F、G、H构成一子系统,类A、B、C是三个client类。A通过连线1、2与D、E相关联,B通过连线3、4与D、H相关联,C通过连线5、6与D、G相关联。为方便A、B、C与子系统的联系,加入facade类:
加入Facade类后,类ABC将不再与子系统内的类直接关联,而子系统内部的联系也并未改变。加上连线后,如图:
类A、B、C只与Facade相联,为完成相应的功能,Facade将分别与DEHG相连。从上可看出Facade模式的一个基本特点:Facade只是为子系统提供一个抽象接口,简化client对子系统的调用;它并未定义新的功能,不影响子系统已有的内部关联。Facade可能只对client可见,而不需对子系统可见。
Mediator模式是作用于上述图中的子系统内部,目的是为了减少子系统内部复杂的关联。对其应用Mediator模式后,结构图如下:
Mediator的特点在于:Mediator是子系统中各类之间相互联系的核心枢扭。各类的接口中只关注于定义自身的功能属性,不将它与其它类的联系代码包含其中,而将其包含到一个中间类Mediator中。实际上,一个子系统的功能是通过各类间的相互作用,即Mediator类,表现出来的。当要改变一个子系统的功能时,只需定义新的Mediator类,而不需改变各类的定义。
(11)Strategy/Bridge
我没看出二者有任何本质的区别?!
(12)Template Method/Strategy
Template Method通过继承把易变算法分离,而Strategy通过创建新类把易变算法隔离。Strategy的优点在于易切换算法,因为算法可以用一个单独的对象表示。而Template method则不方便,如果想切换算法,必须生成一个新的对象,一般新对象与原有对象的大部分数据都是相同的,这意味着有赋值操作或拷贝构造操作。