常见错误69:类型特征码
在面向对象思想中,对象的类型由其行为决定。
在C++语言中,我们绝对不会在采用面向对象思想的代码片段中依类型特征码进行逻辑
分派(switch on type codes)。
对象的类型一经初始化指定,便矢志不渝。
如果在一些罕见的设计中确实要求严格类型特征码的话,最好遵循两个实现方面的指导
原则。首先,不要以数据成员的形式存储特征码。使用虚函数代替它。因为其效果是将类型
特征码更直接地与实际类型(之行为)相关联,而且能够在更广泛的场合下避免对象的分裂。
其次,最好保持基类对于其派生类的一无所知,因为这样会有效地降低继承谱系内的耦合,
还能够满足在维护阶段随时添加或删除派生类种类的需要。这实际上暗示了类型特征码集合
能够在代码之外进行维护,可能遵循某种官方标准类维护类型特征码列表,或指定某种算法或
流程来产生类型特征码集合。
让设计师被迫使用类型特征码的常见情形是面向对象的模块必须与非面向对象的模块交互。
比如,必须从外部读入某种“消息”,而消息的类型是根据某个初始的整型特征码指定的。消息
的长度和其余部分的结构由特征码指定。
一般而论,最好是做出一个设计防火墙。在这种情况下,设计中与消息的外部表示交互的
那个部分会依整型特征码分派,以生成某个并不内含类型特征码的适当对象。而整个设计
而言,则可以简单忽略类型特征码而仅用动态绑定。请注意,如有必要,从对象本身重新
生成原始消息是举手之劳,因为对象要感知其原始的特征码,是并无必要将其以数据成员的
形式加以存储的。
在面向对象思想中,对象的类型由其行为决定。
在C++语言中,我们绝对不会在采用面向对象思想的代码片段中依类型特征码进行逻辑
分派(switch on type codes)。
对象的类型一经初始化指定,便矢志不渝。
如果在一些罕见的设计中确实要求严格类型特征码的话,最好遵循两个实现方面的指导
原则。首先,不要以数据成员的形式存储特征码。使用虚函数代替它。因为其效果是将类型
特征码更直接地与实际类型(之行为)相关联,而且能够在更广泛的场合下避免对象的分裂。
其次,最好保持基类对于其派生类的一无所知,因为这样会有效地降低继承谱系内的耦合,
还能够满足在维护阶段随时添加或删除派生类种类的需要。这实际上暗示了类型特征码集合
能够在代码之外进行维护,可能遵循某种官方标准类维护类型特征码列表,或指定某种算法或
流程来产生类型特征码集合。
让设计师被迫使用类型特征码的常见情形是面向对象的模块必须与非面向对象的模块交互。
比如,必须从外部读入某种“消息”,而消息的类型是根据某个初始的整型特征码指定的。消息
的长度和其余部分的结构由特征码指定。
一般而论,最好是做出一个设计防火墙。在这种情况下,设计中与消息的外部表示交互的
那个部分会依整型特征码分派,以生成某个并不内含类型特征码的适当对象。而整个设计
而言,则可以简单忽略类型特征码而仅用动态绑定。请注意,如有必要,从对象本身重新
生成原始消息是举手之劳,因为对象要感知其原始的特征码,是并无必要将其以数据成员的
形式加以存储的。
不足之处在于当消息集合发生变化时,分派的代码需要同步修改和重新编译。
firewall.h
#ifndef FIREWALL_H
#define FIREWALL_H
#include <vector>
struct RawMsgSource {
RawMsgSource( const char *msg )
: msgcode( msg[0] ), msgBody( msg+1 ) {}
char msgcode;
const char *msgBody; // variable-length
};
class Msg {
public:
virtual ~Msg() {}
virtual void op1() = 0;
};
class MsgType {
public:
virtual ~MsgType() {}
virtual int code() const = 0;
virtual Msg *generate( RawMsgSource & ) const = 0;
};
class Firewall { // Monostate
public:
void addMsgType( const MsgType * );
Msg *genMsg( RawMsgSource & );
typedef std::vector<const MsgType *> C;
typedef C::const_iterator I;
private:
static C *types_;
};
#endif
firewall.cpp
#include "firewall.h"
#include <iostream>
Msg *Firewall::genMsg( RawMsgSource &src ) {
int code = src.msgcode;
for( I i( types_->begin() ); i != types_->end(); ++i )
if( code == (*i)->code() )
return (*i)->generate( src );
return 0;
}
void Firewall::addMsgType( const MsgType *mt ) {
// Perform lazy initialization of container to
// avoid runtime static initialization ordering
// problems.
// In this case, destruction of the container is
// not a problem.
if( !types_ )
types_ = new C;
types_->push_back(mt);
}
Firewall::C *Firewall::types_ = 0;
msg1.cpp
#include "firewall.h"
#include <iostream>
class Msg1 : public Msg {
public:
enum { code = 0x05 };
Msg1( RawMsgSource & )
{}
void op1()
{ std::cout << "Msg1::op1" << std::endl; }
};
class Msg1Type : public MsgType {
public:
Msg1Type()
{ Firewall().addMsgType( this ); }
int code() const
{ return Msg1::code; }
Msg *generate( RawMsgSource &src ) const
{ return new Msg1( src ); }
};
static Msg1Type msg1type;
msg2.cpp
#include "firewall.h"
#include <iostream>
class Msg2 : public Msg {
public:
enum { code = 0x09 };
Msg2( RawMsgSource & )
{}
void op1()
{ std::cout << "Msg2::op1" << std::endl; }
};
class Msg2Type : public MsgType {
public:
Msg2Type()
{ Firewall().addMsgType( this ); }
int code() const
{ return Msg2::code; }
Msg *generate( RawMsgSource &src ) const
{ return new Msg2( src ); }
};
static Msg2Type msg2type;
main.cpp
#include <iostream>
#include "firewall.h"
int main() {
char *rawmsg1 = "\05rest of message";
Firewall fw;
RawMsgSource rms1( rawmsg1 );
if( Msg *m = fw.genMsg( rms1 ) ) {
m->op1();
delete m;
}
else
std::cout << "Unknown message type" << std::endl;
char *rawmsg2 = "\11a different type of message";
RawMsgSource rms2( rawmsg2 );
if( Msg *m = fw.genMsg( rms2 ) ) {
m->op1();
delete m;
}
else
std::cout << "Unknown message type" << std:: endl;
getchar();
return 0;
}
输出
Msg1::op1
Msg2::op1