被误解的C++——高端开发
诸位留神,我要捅马蜂窝了。
呵呵,玩笑。之所以这么说,因为我打算在这里探讨如何利用C++的特性优化高端编程。高端的编程历来是VB、Delphi、Java、C#等等语言的一亩三分地,如果我敢在这里说个不字,那点口水也能把我淹死。不过,为了C++,豁出去了,就让我一个人挨炸弹吧。
我的案例是一个小小的,不起眼的界面问题。尽管是小小的,不起眼的问题,但它的解决为我们指出在高端编程优化的一种途径。这种构想是否可行,以及是否值得,我不清楚。因为所需的机制,还在标准委员会的讨论之中,我还无法制作完整的案例加以检验。
一切从一个ComobBox开始。
有一次,我从数据库提出一组数据,放入一个ComboBox。ComboBox的每个项目还必须和一个值绑定,这样当选中一个项目的时候,便可以得到一个id,用于数据操作。这是再常规不过的操作了。在Windows API,MFC等库中,可以为每个项目指定一个“Item Data”或者“Item Data Pointer”。前者是个long,对应类型为long或能够转化成long的数据;后者是void*,对应那些非整数类型的数据。
在.net中,技术发展了,利用了OOP的多态。ComboBox的Item是一个Object^类型,我们可以创建一个ref class,重定义ToString()方法,而这个类中依然可以保留其他数据。.net中任何类型都可以多态地赋给Object^类型。ComboBox则调用每个Item的Object::ToString()方法,获得所需的显示内容。于是,ComboBox的Item既可以提供ComboBox所需的输出,也解决了附加数据的问题:
class MyItem
{
public:
MyItem(int id, String^ name) : _id(id), _name(name) {}
public:
int_id;
String^_name;
String^ ToString() {
return name;
}
};
combobox1.Add(gcnew MyItem(10, “abc”));
这些操作我在以前已经做过无数次了。但这次不同。我不小心踩了个地雷。这次不同于以前,id不再是long,而是String^。但我还以为是long,所以提取出这个id的时候,出了大错:
MyItem^ cur=dynamic_cast <MyItem^> (combobox1.SelectedItem);
cur-> …; //运行时错误,cur==nullptr
幸好我用了dynamic_cast,不然还不知怎么样呢。因为放入ComboBox的对象不是MyItem类型,所以这个转换得到的是空句柄。于是,我的程序得到了了一个难看的运行时错误。
“唉,要是强类型多好!”我一声长叹。话音未落,一个念头瞬间闯入了我的脑海,就像漆黑的夜空,划过一道明亮的闪电,照亮了每一个角落。…
呵呵,没那么夸张。但我的确想到了“强类型”。
毋庸置疑,强类型可以为我们提供很多好处。首先,强类型可以减少类型错误,避免不必要的类型转换,确保类型安全;其次,由于ADT(抽象数据类型)的引入,使得类型在拥有数据结构的同时,也描述了外在的行为特征。因此,类型具备了描述业务逻辑的能力。于是,强类型化使得我们可以利用ADT的这些能力,将业务逻辑的约束直接映射到代码中。并且,利用类型的匹配机制,维持了这些约束,使我们得以在编译时拦截诸多与业务逻辑相关的错误;最后,强类型可以参与重载,使得我们可以用统一的形式编写具有相同逻辑含义的代码,以此优化软件开发。
当我想到强类型,立刻联想到模板。我们完全可以利用模板来解决这个问题。
我考虑了三种方案,(所有三种方案,我放在blog里,以节省篇幅),最终选择了一种组合方案:
首先,需要约定业务对象类(以管理员类Admin为例)提供一个默认的显示字符串构造函数,暂且称为DefDisp()。(通常,业务对象类都应该有一个默认的显示函数,为了便于在界面上显示)。然后,定义一个默认显示组织的函数对象:
template <typename T>
struct DefaultDispOp
{
string operator()(const T& item) {
returnitem.DefDisp();
}
};
最后,将DefaultDispOp作为XComboBox第二参数的默认参数:
template <typename ItemT, typename DispOP=DefaultDispOp <ItemT> >
class XComboBox
{
...
string GetItemDispString(const ItemT& item, DispOp& op) {
returnop(item);
}
}
DefDisp()所对应的显示应该是该业务对象最常用的显示,比如对于Admin而言,可能就是全名。如果只需要用默认方式显示的,那么只需简单地用业务对象类实例化XComboBox即可:
XComboBox <Admin> combobox1;
XComboBox <Admin> combobox2;
而对于有特殊显示要求的,再另外提供转换函数对象。
struct AdminDisp1
{
string operator()(const Admin& admin) {
…;
}
};
//用Admin和AdminDisp1实例化XComboBox
XComboBox <Admin, AdminDisp1> combobox1;
三种方案的分析可以看出,模板和泛型编程过度使用,反而不会达到最好的效果。通常,深度的泛型编程(包括元编程)都是在“迫不得已”的情况下使用的。比如,不使用GP,便会造成巨大的开发工作量(就像货币系统那个案例那样),或者根本无法实现(如标准库中的通用算法)。一般情况下,能使用传统技术,优先考虑传统技术,或者简单的泛型技术。
作为一个好事者(我总是一个好事者),我不断地端详XComboBox,发现它像一个东西:容器。没错,ComboBox原本就是一个容器,只是功能更多些而已。既然是个容器,我是否可以把它用在标准算法上呢?当然可以。我们可以把它看作是一个vector或者dequeue或者list,也可以象操作序列容器那样操作XComboBox,只需为XComboBox增加一组成员和typedef:
template <typename ItemT, typename DispOP>
class XComboBox
{
public:
…
Iterator begin(){…}
Iterator end() {…}
void push_back(const ItemT& item) {…}
void pop_back() {…}
…
};
这样,我们便可以将一个容器中的业务对象直接copy到XComboBox中去:
XComboBox <Admin> combo1;
vector <Admin> vAdmins;
…//初始化vAdmins,比如来源于某种对象数据系统
copy(vAdmins.begin(), vAdmins.end(), back_inserter(combo1));
更进一步,如果我们使用的数据库结果集也兼容标准库容器。那么,可以用transform算法直接将一个结果集传递到XComboBox中:
struct RS_to_Admin
{
Admin operator()(const STLRecordSet::RowT& row) {
Admina;
…//用结果集的行初始化业务对象
returna;
}
};
XComboBox <Admin> combo1;
STLRecordSet rs=QueryData(“…”);
tramsform(rs.begin(), rs.end(), back_inserter(combo1), RS_to_Admin());
这些也表明,一旦所有的应用组件,包括数据访问、界面元素等等,都STL化之后,整个应用系统的开发便可以大幅度的简化。在这方面,走在前面的是Mathew Wilson(他的《Imperfect C++》想必很熟吧)。Wilson所开发的STLSoft以及相关的STLxxx系列库为诸多系统包括Unix、MFC、ATL、Internet、COM、.net提供了STL化的包装层。使得标准算法得以发挥最大的作用。
[解决办法]
支持LZ的研究!:)
界面组件是否需要模版化,这是一个两难课题。
一方面,正如LZ所说,弱类型意味着必须强制类型转换,而强转是许多错误的根源;
另一方面,模版化确实加大了界面组件的复杂度;并且,这种复杂度是否必要呢?
除了这两种做法,有以下思路可以参考:
1、参考MVC的做法(当然不是照搬,只是借鉴它的解耦方法),由容器存储数据,由界面组件显示数据,再由专门的操作类将数据从容器中注入界面组件以显示。这样,界面组件可以专注于显示;毕竟显示的复杂度已经比较大,再加入GP感觉有些偏重了。
2、界面组件不使用类似void*的弱类型关联方式,使用固定的一种或几种关联(其中一种可以是long型的ID,其他几种也不能有类似void*的弱类型)。这样就可以减少出错了。
以上两点可以分别使用,也可以一起使用。
但是,这两点也都有局限性。第一点形成了包含三个部分的一个小框架,虽然其中两部分可以重用现有的界面组件及容器类,但是仍然是增加了复杂度。而且,如果要做得比较规范,其操作类还是要用GP手段。所以综合看来,复杂度没有下降,只是增加一个中间层,使界面组件和数据解耦而已。
第二点是牺牲灵活性以换取安全性的做法。其局限性就不必多说了。
所以在界面这方面如何取舍,是比较难做决定的。这可能就是C++没有提供标准界面库的原因之一。当然LZ提供的思路值得借鉴。这里没有谈及它的好处,只是因为这些都已经在LZ的文中一览无余,无须我画蛇添足了,呵呵。
[解决办法]
我不断地端详XComboBox,发现它像一个东西:容器。
很佩服LZ的思维
我想LZ的意思是
在我们的意识中
C++不适合做上层的开发(在开发效率的角度看待问题)
但是如果我们能善于发现问题
并且善于解决问题
你比如说LZ把STL中的算法运用到上层开发中
是可以提高开发效率的
[解决办法]
回复人:xenix(早死三年何愁睡) ( 二级(初级)) 信誉:100 2007-6-14 19:03:38 得分:0
?
两大泛型界面库: win32gui和smartwin,有真正的用户吗?几乎没有
=============================================================
有,我就在用赫赫
[解决办法]
谁说 GUI 就不是所谓“高端”?
问这种问题的人是不是学计算机的
你们是不是认为 GUI 很简单呀?