读书人

强大的C++千人一面解决方案

发布时间: 2012-02-29 16:44:11 作者: rapoo

强大的C++——千人一面
千人一面
我有一个梦:用统一的方式访问所有的类型的数据。呵呵,只是一个梦而已。至少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) {
stringconnection_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;
};
多态化后,适配器具备了类型的一致性,可以更灵活的加以运用。
使用适配器非常简单:
XmlFromNetxml_net_;
XmlFromDBxml_db_;
XmlFromDiscxml_disc_;

stringcfg_net_(…), cfg_db_(…), cfg_disc_(…);

AdpXmlFromNetadp_net_(xml_net_);
AdpXmlFromDBadp_db_(xml_db_);
AdpXmlFromDiscadp_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) {
stringconnection_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_);
你也许会说:“嗨,这只不过是个字面的把戏而已。我只要为适配器规定一个便于记忆的命名规则,就可以达到同样的效果。”
没错,我承认,优化的命名规则可以达到这样的效果。但是却达不到以下的效果:
XmlFromNetxml_net_;
XmlFromDBxml_db_;
XmlFromDiscxml_disc_;

stringcfg_net_(…), cfg_db_(…), cfg_disc_(…);

xmldocxd_net_=UniLoad(cfg_net, cfg_net_);
xmldocxd_db_=UniLoad(cfg_db, cfg_db_);
xmldocxd_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);
returnadp_.GetXml();
}
这是一个我们通常所说的helper函数。是个函数模板。关键在于,函数模板可以通过调用时的实参推导出模板参数T。然后用T实例化Adapter <> ,获得适配器类。然后创建对象,加载xml。因此,我们无须给出类型,便可构造正确的Adapter <> 匹配。
很好了吧!在我的榆木脑袋里,这已经是最好的结果了。
让我再次感谢那些C++大师们,是他们赋予C++模板和模板特化这种强大的机制,允许我们安全、可靠、稳定、高效地优化代码。
不过,这才是开始。我们里只用到了模板全特化。如果使用全特化的孪生兄弟——局部特化,我们可以应付更复杂的情况。下次再说。


[解决办法]
mark
[解决办法]
牛人啊, JF
[解决办法]
学习
[解决办法]
赞一个!

感谢LZ的无私分享!

父类中的接口,应该是纯虚函数吧?
virtual bool Load(const string& config_string)=0;

------------
《Effective C++ 2ed》条款14:
虚函数的目的是让派生类去定制自己的行为(见条款36),所以几乎所有的基类都包含虚函数。
如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。

[解决办法]

[解决办法]
C++的好处
感谢楼住 先珍藏了
[解决办法]
现在的水平还无法欣赏楼主的文章,不过我知道文章内容水平很高,MARK。BUCKUP。
[解决办法]
设计模式看多了,无聊。
[解决办法]
C++确实太强大了,只有想不到没有做不到.
------解决方案--------------------


谢谢楼主!
让我大开眼界!

[解决办法]

学习

[解决办法]
学习啊,。。。。。。
[解决办法]
大家可以去看“关于二维矩阵两点间步数计算的问题 ”吗?
[解决办法]
没看。。。但是大家说好,就一定是好贴了,UP!

[解决办法]
learning!
[解决办法]
没看
估计是泛型吧
[解决办法]
stl中的stack,queue都是适配器,实现方式基本也和楼主的一样吧。
很强,不过看这样的代码,估计公司review,1w%过不了。
[解决办法]
现在还看不懂,学习中...
[解决办法]
哎呀,强人啊
[解决办法]
就讲了个adapter模式

读书人网 >C++

热点推荐