上面一文中,我们描述了对象的创建和销毁。从试图去掉对象初始化过程中的硬编码开始,我们逐渐推导出了几个各个GoF中的创建型模式。现在,有了对象,我们看看对象的行为。
我们从最基本的需求开始,如何使得对象在运行过程中行为可变?
这是一个非常一般的问题,我们从传统的方法说起。简单地说,对象的行为取决于对象的状态,而对象的状态由构成对象的成员的状态决定。这是毫无疑问的。所以明显的答案是改变对象的状态,最直观的是提供设置对象状态的接口,这样,通过操纵对象状态(直接修改或者对象某一行为的副作用),我们可以期望对象可以表现出不同的状态。从实现的角度看,对象的行为实现中包含了对对象状态的处理,因而可以对于同样的外部刺激(接收到同样的消息)给出不同的反应。
因为OO强调的是针对接口编程,而不是实现,所以上面的描述可以进一步表述为“对于特定的型的实现,如果在运行是改变其状态?”。敏感的OO程序员已经可以意识到,多态不就是解决这个问题的嘛!正是如此。如果我们可以使用不同的对象实现同样的型,自然可以提供不同的外部表现行为。多态正式应对此需要的技术。请注意其中的关键,是现实的需求催生了多态技术成为OO的基础,而不是相反。
OO之上论者喜欢强调“一切皆对象”。尽管这一说法受到不少的怀疑和批评,其表现的实现还是值得我们关注。研究一下一个对象如何接受一个消息。最简单的形式是
foo->bar(7);
使用OO术语描述为:想对象foo发送消息bar,消息参数是整数7。所以从概念上讲其等价于以下调用
send_message(foo, bar(7)); // 假设可以从整数直接构造消息本身
使用这个等价的表达,我们其实可以看出,这里,消息本身已经是另一个类型的对象了。当消息本身可以表达不同的信息,我们自然可以期望接受消息的对象可以给出不同的行为。如果把要发送消息的对象已经消息本身组合起来,作为一个独立的对象,那么这个对象已经有了执行一个请求的全部信息,可以在任何方便的时候执行请求。而作为一个整理的对象,可以利用其它机制在各个对象之间传递,而对象的调用却被完整地隐藏起来。消息的发送者在构造了该对象以后再也不用关心后续是怎么处理的了(有时候如果它关注执行的结果,那么可以令该对象之执行完毕的时候再通知它既可)。这意味这对象的发送和接受解耦了。在分布式系统中,莫不如此。这是什么呢?Command模式!
现在看对象行为的实现。一般来说,对象行为实现为一系列的指令,提供或者不提供输出。无论如何,对于特性的行为实现,指令序列是固定的。那么除了上面提到的方法,怎么让行为结果随着需求的不同而变化呢?
编程领域有句俗话,说的是“任何问题都可以通过引入一个额外的中间层解决”。什么意思?
假设我们所有这些行为指令放在一个函数中,引入一个中间层是什么意思?两次函数调用?对,也不对。说它不对,是因为对调用一次本质上不能解决任何问题;说它对,是因为这个第二次的函数调用提供了一个机会,你可以通过任意你喜欢的方式调用这个函数,比如使用函数指针!。
任何有经验的C程序员都明白,稍微正式一点的程序都会用到函数指针,如异步支持,OO实现,命令解析等等。通过动态置换第二次调用函数指针,我们可以确保对于相同的接口给出不同的行为。其实,这是一种行为plug-in。可以可以把任何的行为实现动态插入。
C++中,函数指针的wrapper一般叫做functor,就是那种提供了函数调用操作符的对象(又是对象!)。现在情况简单了,我们使用一个型来定义某种行为接口,然后实现这种接口,最后,把该型的弱引用(对应于C++中的指针)作为成员。这样,在需要的时候,我们可以通过设置不同的实现来获得不同的行为了。似乎有点复杂,下面是代码的例子:
struct do_something { virtual void operator(int) = 0; };
class foo
{
do_something* functor_;
public:
void set_functor(do_something* f) { functor_ = f; }
void bar(int v) { functor_(v); }
};
这是什么呢? Strategy模式!一般来说,各种不同的strategy或者算法是相互独立的,可以根据不同的情况在运行时配置。
初看起来,Command模式和Strategy模式结构很像,都是使用单独的对象封装行为本身。但是Strategy强调的是行为的可互换,而Command强调的则是消息发送者与消息接受者的隔离。注意,意图才是决定设计选择的关键。
在上面的代码中,如果行为接口已经定义了高层次的执行绪序列,那么代码看起来是这个样子:
struct do_something
{
void operator(int v)
{
if (!do_check(v)) return
do_impl(v);
}
protected:
virtual void do_impl(int) = 0;
virtual bool do_check(int) = 0;
};
class my_impl : public do_something
{
protected:
virtual void do_impl(int) {...}
virtual bool do_check(int) {...}
};
class foo
{
do_something* functor_;
public:
void set_functor(do_something* f) { functor_ = f; }
void bar(int v) { functor_(v); }
};
客户代码可以这样:
foo f;
f.set_functor(new my_impl);
f.bar;
这种上次代码定义了实现框架,下层代码完成实现具体步骤的叫做什么呢?Template Method模式!
假设后来需要要求你对输入参数做额外的检查。那该怎么办呢?OO的基本原则之一是OSP,及open to extention but close to modification。按照此原则,我们可以如下实现:
class my_impl_v2 : public my_impl
{
protected:
virtual bool do_check(int v)
{
if (!my_impl::do_check(v)) return false;
//do you own check here
}
};