强类型ComboBox的实现方案

为了实现强类型的ComboBox,我写下了这么一段代码:
template<typename ItemT>
class XComboBox
{
public:
 …
 void AddItem(const ItemT& item) {
  …
 }
 ItemT& GetItem(int index) {
  …
 }
 ItemT& SelectedItem() {
  …
 }
 …
};
为了节省篇幅,我略去了具体实现的代码。这些接口便足以说明问题了。当ComboBox变成模板后,Item的类型也就静态化了。因此,我们可以直接访问Item对应的数据,而不必进行类型转换了:
MyItem  mi1;

XComboBox<MyItem> combobox2;
combobox2.AddItem(mi1);

MyItem& cur=combobox2.SelectedItem();

这样我就不会再把Item的类型搞错了。如果类型不对,编译时便会报错,而不必等到运行时丢人现眼了。
不过,事情还没完。我进一步发挥想象力,如果我不必编写专门的Item类,直接使用业务类,不就更省事吗?假设有一个ComboBox,对应的数据是管理员。我们有一个类对应管理员:
class Admin
{
public:
 long id() {
  …
 }
 string GetFirstName() {
  …
 }
 string GetLastName() {
  …
 }
 string Address() {
  …
 }
 …
};
我设想中的效果就是:
XComboBox<Admin> combobox3;
combobox2.Add(admin1);
combobox2.Add(admin2);

cout<<”id:”<<combobox3.SelectedItem().id()<<”/n”;
cout<<”name:”<<combobox3.SelectedItem().GetFirstName()<<” “
  << combobox3.SelectedItem().GetLastName();

这中间就存在一些问题了。XComboBox并不知道使用者打算怎么显示Admin中的数据。或许使用者喜欢直接用“first name+last name”,或许使用者喜欢用“last name, first name”,或许还需要在姓名前加上id。反正在编写XComboBox的代码时,我们不可能知道使用者的意图。为此,我们需要采取一些附加措施,解决这个问题。我考虑了这样几种可能方法:
1. 要求业务对象类Admin提供一个特定的函数,比如Display(),提供显示内容的组织。
2. 利用某个特定的操作符,实现显示内容的组织。
3. 在XComboBox模板中增加一个模板参数,用于指定一个用于显示组织的函数对象。
第一种方案,通过约定业务对象必须提供一个组织显示字符串的函数Display():
template<typename ItemT>
class XComboBox
{
 …
private:
 string GetItemDispString(const ItemT& item) {
  return item.Display();
 }
 …
};
内部函数GetItemDispString()通过访问ItemT的实例上的Display()函数,获得所需的显示数据。但由于一个Display函数只能提供一种显示模式,而一个业务对象可能在不同的界面中,需要以不同的模式显示数据。所以,这种方法不能成立。
操作符方案中,对操作符有一个要求,必须是可以在类外定义的。否则,将和Display()函数是一回事。流操作符<<是比较好的选择(>>也可以,他们是等价的)。XComboBox调用>>操作符,将业务对象转换成显示
template<typename ItemT>
ItemDisp& operator<<(ItemDisp& disp, const ItemT& item) {
 … //实现显示数据的组织
}

template<typename ItemT>
class XComboBox
{
 …
 void GetItemDispString(ItemDisp& disp, const ItemT& item) {
  disp<<item;
 }
};
重载<<操作符便可以将业务对象的数据按要求格式化成显示数据。其中ItemDisp是预定义的一个类,用于存放数据转换後的结果。但是,由于一对参数类型只能重载一次。而对于不同界面中出现的XComboBox<Admin>可能需要提供多个<<的重载。这种情况下,我们无法为XComboBox<Admin>提供不同的显示模式。
解决的办法是利用模板的非类型模板参数:
template<int DispID>
class ItemDisp
{…};

template<typename ItemT, int DispID>
class XComboBox
{
 …
 void GetItemDispString(ItemDisp<DispID>& disp, const ItemT& item) {
  disp<<item;
 }
};

//使用时,首先重载显示组织所需的<<操作符
ItemDisp<1>& operator<<(ItemDisp<1>& disp, const Admin& item) {
 …
}
//然后实例化XComboBox模板
XComboBox<Admin, 1> combobox1;
//需要另外一种显示模式,重载另一个<<操作符
ItemDisp<2>& operator<<(ItemDisp<2>& disp, const Admin& item) {
 …
}
XComboBox<Admin, 2> combobox2;

由于ItemDisp<1>和ItemDisp<2>是不同的类型,所以,两个<<的重载不会引发歧义。但是,XComboBox的第二个模板实参必须和ItemDisp中的实参一致。这种方案显得有些笨拙,使用起来也不方便。
第三种方案,实际上是标准库关联容器的一个翻版。通过第二个模板参数允许使用者提供自定义的操作:
template<typename ItemT, typename DispOP>
class XComboBox
{
 …
 string GetItemDispString(const ItemT& item, DispOp& op) {
  return op(item);
 }
}
//使用时,需要定义数据的显示转换操作函数对象
struct AdminDisp1
{
 string operator()(const Admin& admin) {
  …;
 }
};
//用Admin和AdminDisp1实例化XComboBox
XComboBox<Admin, AdminDisp1> combobox1;
//另一种显示
struct AdminDisp2
{
 string operator()(const Admin& admin) {
  …;
 }
};
//用Admin和AdminDisp2实例化XComboBox
XComboBox<Admin, AdminDisp2> combobox2;
更进一步,我们可以将方案1同方案3相结合,提供更方便的使用。
首先,需要约定业务对象类提供一个默认的显示组织函数,暂且称为DefDisp()。(通常,业务对象类都应该有一个默认的显示函数,为了便于在界面上显示)。然后,定义一个默认显示组织的函数对象:
template<typename T>
struct DefaultDispOp
{
 string operator()(const T& item) {
  return item.DefDisp();
 }
};
最后,将DefaultDispOp作为XComboBox第二参数的默认参数:
template<typename ItemT, typename DispOP=DefaultDispOp<ItemT> >
class XComboBox
{…}
DefDisp()所对应的显示应该是该业务对象最常用的显示,比如对于Admin而言,可能就是全名。如果只需要用默认方式显示的,那么只需简单地用业务对象类实例化XComboBox即可:
XComboBox<Admin> combobox1;
XComboBox<Admin> combobox2;
而对于有特殊显示要求的,再另外提供转换函数对象。
从前面三种方案的分析可以看出,模板和泛型编程过度使用,反而不会达到最好的效果。通常,深度的泛型编程(包括元编程)都是在“迫不得已”的情况下使用的。比如,不使用GP,便会造成巨大的开发工作量(就像货币系统那个案例那样),或者根本无法实现(如标准库中的通用算法)。一般情况下,能使用传统技术,优先考虑专通技术,或者简单的泛型技术。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值