强大的C++——千人一面

longshanks 2007-04-30 08:30:42
千人一面
我有一个梦:用统一的方式访问所有的类型的数据。呵呵,只是一个梦而已。至少C++没有为我直接提供这么好的功能。我接触过的其他语言,也没有。
不过,我们至少可以为若干类型提供统一的访问接口。只要使用适配器就可以了。
假设,我们有三个类,分别从网上、数据库和本地文件中读取字符串流(xml格式),然后转换成xml文档:
class XmlFromNet
{
public:
bool Login(const string& address, const string& uid, const string& psw)
bool Load(const string& file_name);
xmldoc GetXml();

};

class XmlFromDB
{
public:
bool Connect(const string& connection_string);
bool Load(const string& command);
xmldoc GetXml();

}

class XmlFromDisc
{
public:
bool Load(const string& file_name);
xmldoc GetXml();

};
我现在需要用一个配置字符串统一加载数据。配置字符串无非是将登陆或连接,以及数据获取的字符串拼接在一起。如果这些类是我做的,那好办,只要在三个类中各增加一个函数即可。但这三个类是一个缺乏预见性的程序员开发的,但我们没有权利修改源码。
怎么办?不难,我们可以为每一个类编写一个适配器类,这个适配器类的作用仅仅是提供统一的接口实现这些类的访问:
class Adp_XmlFromNet
{
public:
Adp_XmlFromNet(XmlFromNet& obj)
: _obj(&obj) {}
bool Load(const string& config_string) {
string address_, uid_, psw_, file_name_;
… //从config_string中解析出address_等参数
_obj->Login(address_, uid_, psw_);
_obj->Load(file_name_);
}

private:
XmlFromNet* _obj;
};

class Adp_XmlFromDB
{
public:
Adp_XmlFromNet(XmlFromDB& obj)
: _obj(&obj) {}
bool Load(const string& config_string) {
string connection_string_, command_;
… //从config_string中解析出connection_string_等
_obj->Connect(connection_string);
_obj->Load(command_);
}

private:
XmlFromDB* _obj;
};

class Adp_XmlFromDisc
{
public:
Adp_XmlFromNet(XmlFromDisc& obj)
: _obj(&obj) {}
bool Load(const string& config_string) {
string file_name;
… //从config_string中解析出file_name
_obj->Load(file_name);
}

private:
XmlFromDisc* _obj;
};
每个适配器类提供统一的Load()成员函数,实现加载xml文档的操作。注意,他们每个都是不一样的。更进一步,可以让这些类继承自同一个抽象基类,以提供多态特性:
class IAdaptXml
{
public:
bool Load(const string& config_string)=0;
};
多态化后,适配器具备了类型的一致性,可以更灵活的加以运用。
使用适配器非常简单:
XmlFromNet xml_net_;
XmlFromDB xml_db_;
XmlFromDisc xml_disc_;

string cfg_net_(…), cfg_db_(…), cfg_disc_(…);

AdpXmlFromNet adp_net_(xml_net_);
AdpXmlFromDB adp_db_(xml_db_);
AdpXmlFromDisc adp_disc_(xml_disc_);

adp_net_.Load(cfg_net_);
adp_db_.Load(cfg_db_);
adp_disc_.Load(cfg_disc_);
OK,接口完全统一。但是,这似乎不是最好的。因为每个适配器的名称都不一样,而且都很冗长,不便于记忆。如果xml的访问类更多些的话,比如几十个(不要笑,不少类库的确会为你提供大量不同的类,以支撑一系列相关的功能。.net就是代表。不然它那几千上万的类是哪儿来的?),相应的适配器也很多,很难记住那么多名字。如果能够用更统一的方式实现就更好了。直说吧,就是所有的适配器都使用一个名字。
什么?宏。不要提宏,宏很丑陋,不是类型安全的,而且不便于调试。
对了,模板。我们希望达到这个效果:
Adapter<XmlFromNet> adp_net_(xml_net);
很好吧。可你也许会想,模板需要类型参数有相同的接口(成员)形式。没错,这是个问题。但是我们有秘密武器,功能无比强大,但却被Java之流抛弃的秘密武器——特化!
感谢Stroustrup、Stepanov等各位大师,为C++配备了如此强大的尖端武器。利用它我们就能轻而易举地解决这个问题:
template<class T>
class Adapter; //没有模板定义,目的是:当你用一个没有在这里特化的类实例化模 //板的时候,能够在编译时给出一个错误。而不是在运行时给出古怪 //的行为。

template<>
class Adapter<XmlFromNet>
{
public:
Adapter(T& obj)
: _obj(&obj) {}
bool Load(const string& config_string) {
string address_, uid_, psw_, file_name_;
… //从config_string中解析出address_等参数
_obj->Login(address_, uid_, psw_);
_obj->Load(file_name_);
}

private:
T* _obj;
};

template<>
class Adapter<XmlFromDB>
{
public:
Adapter(T& obj)
: _obj(&obj) {}
bool Load(const string& config_string) {
string connection_string_, command_;
… //从config_string中解析出connection_string_等
_obj->Connect(connection_string);
_obj->Load(command_);
}

private:
T* _obj;
};

template<>
class Adapter<XmlFromDisc>
{
public:
Adapter(T& obj)
: _obj(&obj) {}
bool Load(const string& config_string) {
string file_name;
… //从config_string中解析出file_name
_obj->Load(file_name);
}

private:
T* _obj;
};
好了,核心代码一样,只不过用模板和特化的模板代替了原来的类。开发工作量还是一样的,只不过多打了几个字而已。这下我们就可以像设想的那样使用适配器了:
Adapter<XmlFromNet> adp_net_(xml_net_);
Adapter<XmlFromDB> adp_db_(xml_db_);
Adapter<XmlFromDisc> adp_disc_(xml_disc_);

adp_net_.Load(cfg_net_);
adp_db_.Load(cfg_db_);
adp_disc_.Load(cfg_disc_);
你也许会说:“嗨,这只不过是个字面的把戏而已。我只要为适配器规定一个便于记忆的命名规则,就可以达到同样的效果。”
没错,我承认,优化的命名规则可以达到这样的效果。但是却达不到以下的效果:
XmlFromNet xml_net_;
XmlFromDB xml_db_;
XmlFromDisc xml_disc_;

string cfg_net_(…), cfg_db_(…), cfg_disc_(…);

xmldoc xd_net_=UniLoad(cfg_net, cfg_net_);
xmldoc xd_db_=UniLoad(cfg_db, cfg_db_);
xmldoc xd_disc_=UniLoad(cfg_disc, cfg_disc_);
很奇妙?那当然。Adapter<>根本没出现,却以统一的方式加载了xml。让我们来看一下UniLoad()干了些什么:
template<class T>
xmldoc UniLoad(T& xml, const string& config) {
Adapter<T> adp_(xml);
adp_.Load(config);
return adp_.GetXml();
}
这是一个我们通常所说的helper函数。是个函数模板。关键在于,函数模板可以通过调用时的实参推导出模板参数T。然后用T实例化Adapter<>,获得适配器类。然后创建对象,加载xml。因此,我们无须给出类型,便可构造正确的Adapter<>匹配。
很好了吧!在我的榆木脑袋里,这已经是最好的结果了。
让我再次感谢那些C++大师们,是他们赋予C++模板和模板特化这种强大的机制,允许我们安全、可靠、稳定、高效地优化代码。
不过,这才是开始。我们里只用到了模板全特化。如果使用全特化的孪生兄弟——局部特化,我们可以应付更复杂的情况。下次再说。
...全文
1319 36 打赏 收藏 转发到动态 举报
写回复
用AI写文章
36 条回复
切换为时间正序
请发表友善的回复…
发表回复
yjukh 2007-09-14
  • 打赏
  • 举报
回复
回头再看~
losky 2007-09-14
  • 打赏
  • 举报
回复
适配器....
liwei84516 2007-09-14
  • 打赏
  • 举报
回复
太高深了,路过,顶下
herman~~ 2007-09-14
  • 打赏
  • 举报
回复
上班中,做个标记先,有空看
loops 2007-09-14
  • 打赏
  • 举报
回复
收藏
wishfly 2007-09-13
  • 打赏
  • 举报
回复
up
WinWing 2007-09-10
  • 打赏
  • 举报
回复
Mark~
yuyunliuhen 2007-09-10
  • 打赏
  • 举报
回复
mark
xmoon1983 2007-09-10
  • 打赏
  • 举报
回复
同LS的,收藏先。
roadtang 2007-09-10
  • 打赏
  • 举报
回复
被LZ另一个贴子吸引过来, 。。。
资深码农多年 2007-05-01
  • 打赏
  • 举报
回复
也可以用STL常见的手法trait达到目的
longshanks 2007-05-01
  • 打赏
  • 举报
回复
to gooderfeng(冯贵来):
stack、queue的实现是不同的。主要是因为两者的目的不同。stack和queue是将另一个容器包装成某种形式。而这里的Adapter是将一系列不同的类包装成统一的形式。
snprintf 2007-05-01
  • 打赏
  • 举报
回复
mark
奶糖人五号 2007-05-01
  • 打赏
  • 举报
回复
不错的贴,我顶
michaeldg 2007-04-30
  • 打赏
  • 举报
回复
谢谢楼主!
让我大开眼界!
neohost 2007-04-30
  • 打赏
  • 举报
回复
C++确实太强大了,只有想不到没有做不到.
leechiyang 2007-04-30
  • 打赏
  • 举报
回复
设计模式看多了,无聊。
roger_77 2007-04-30
  • 打赏
  • 举报
回复
很少见到CSDN论坛上如此优秀的技术帖子
isarc 2007-04-30
  • 打赏
  • 举报
回复
现在的水平还无法欣赏楼主的文章,不过我知道文章内容水平很高,MARK。BUCKUP。
yixiao386 2007-04-30
  • 打赏
  • 举报
回复
C++的好处
感谢楼住 先珍藏了
加载更多回复(16)

64,646

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧