AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > VC编程

Visual C++泛型编程实践

51自学网 2015-08-30 http://www.wanshiok.com

  第三步:改进(重构)

  重构是一个最近很流行的程序设计思想,说白了就是对已有程序进行改进,在不改变程序外在行为的前提下对程序结构及设计进行改进,以使程序代码更清晰、程序更健壮、更易于维护。

  第一次改进:使用函数重载减少接口名称数量对于添加成员,我们可以直接使用C++的函数重载技术改进如下:

size addMember(Account* e); //添加帐户
{
 //获取下一个可用的ID号
 size id=getNextAccountID();
 itsAccounts[id]=e;
 return id;
}
size addMember(Employee* e); //添加帐户
{
 //获取下一个可用的ID号
 size id=getNextEmployeeID();
 itsEmployees[id]=e;
 return id;
}

  这样一来,消除了对不同表进行操作时调用的函数名称的差异,但我们可以看出,这两个函数的操作逻辑是完全一样的,变化的部分与参数相关,这正是模板技术可以发挥作用的地方,但如何将不同的表添加方法与不同的ID号获取方法及对应的map联系起来呢?

  我们再来看删除函数:由于不同表的删除方法均只有一个相同类型的参数size ID,而函数重载必须要有不同的参数列表,所以,要想实现一个void delMember(size ID)分别对应不同的表的删除操作好象是不可能的,getMember(size ID)方法也是一样,它对不同的表操作虽然有不同的返回值,但参数也是一样的,所以,也不能运用C++内的函数重载方法来实现函数接口命名的一致化。而获取下一个可用ID的函数方法甚至连参数都没有,怎么办呢?看来我们没有办法了。

  幸运的是,Andrei Alexandrescu在他的《 C++设计新思维——泛型编程与设计模式之应用》一书中为我们提供了一种解决办法: Type2Type——它是一个可用于代表参数类型,以让你传递给重载函数的轻量级的ID,其定义如下:

Template <typename T>
Struct Type2Type
{
typedef T OriginalType;
};

  它没有任何数值,但其不同型别却足以区分各个Type2Type实体,而这正是我们所要的。现在,让我们来先解决addMember成员函数中的获取下一个可用ID号的函数,我们可以定义一个重载的函数如下:

size getNextMemberID(Loki:: Type2Type<Employee>)
//对应职员操作
{
 if (itsEmployees.empty())
  return 1;
 std::map<size,Employee*>::iterator it=itsEmployees.
 end();
 --it;
 return it->first+1;
}
size getNextMemberID(Loki:: Type2Type<Account>)
//对应帐户操作
略..

  相应的,删除类函数定义如下:

void delMember(size ID, Loki:: Type2Type<Account>)
void delMember(size ID, Loki:: Type2Type<Employee>)

  获取类函数定义如下:

Account* getMember(size ID, Loki:: Type2Type<Account>)
Employee* getMember(size ID, Loki:: Type2Type<Employee>)

  这样,我们的函数接口就比刚开始的方法更清晰,我们的大脑中要记住的函数名就要少多了。

  第二次改进:使用模板技术减少接口函数数量经过第一次的改进,我们的接口结构比初始的方案要更清晰,但它似乎还存在一个问题:软件大师Martin Fowler在他的著作《重构——改善既有代码的设计》中将之列为代码的坏味道之首——代码重复。我们可以看到,添加、删除、获取的函数实现中,几乎完全是一样的实现逻辑,只不过所操作的map变量不同而已,如下(以添加为例):

size addMember(Account* e); //添加帐户
{
 //获取下一个可用的ID号
 size id= getNextMemberID(Loki::Type2Type<Account>());
 itsAccounts[id]=e;
 return id;
}
size addMember(Employee* e); //添加帐户
{
 //获取下一个可用的ID号
 size id=getNextMemberID(Loki::Type2Type<Employee>());
 itsEmployees[id]=e;
 return id;
}

  如果我们能有办法根据不同的参数获得不同的要操作的map变量,那么这两个方法完全可以实现为一个模板方法如下:

template<typename T>
size addMember(T* e)
{
 size empid=getNextMemberID(Loki::Type2Type<T>());
 //关键在于以下函数
 std::map<size,T*>& its=getMap(Loki::Type2Type<T>());
 its[empid]=e;
 return empid;
}

  如果getMap()方法能实现,那么,我们的模板方法就可以成功。有了前面的铺垫,这个应该水到渠成:

std::map<size,Account*>& getMap(Loki::Type2Type<Account>)

 return itsAccounts;

std::map<size,Employee*>& getMap(Loki::Type2Type
<Employee>);

 return itsEmployees;

  这样我们就可以将所有的添加、删除、获取函数进行模板化实现如下:

template <typename T>
size getNextMemberID(Loki::Type2Type<T>)
{
 std::map<size,T*>& its=getMap(Loki::Type2Type<T>());
 if (its.empty())
  return 1;
  std::map<size,T*>::iterator it=its.end();
  --it;
  return it->first+1;
}
template <typename T>
size addMember(T* e)
{
 size empid=getNextMemberID(Loki::Type2Type<T>());
 std::map<size,T*>& its=getMap(Loki::Type2Type<T>());
 its[empid]=e;
 return empid;
}
template <typename T>
T* getMember(size memberID,Loki::Type2Type<T>)
{
 std::map<size,T*>& its=getMap(Loki::Type2Type<T>());
 return its[memberID];
}
template <typename T>
void delMember(size memberID,Loki::Type2Type<T>)
{
 std::map<size,T*>& its=getMap(Loki::Type2Type<T>());
 its.erase(memberID);
}

  这样,对于本例中6个表分别实现添加、删除、获取成员三组方法,我们总共需要用:四个模板化函数、以及一组分别针对6个表的getMap重载函数。然后,我们每增加一个表,只需要为getMap方法添加一个重载的实现,与初始设计中的4*6=24种名称各不相同,每增加
一个表支持,要添加4种不同名称的函数实现的方案比较起来,是不是更清晰、更易维护、易于扩展了呢?

  第四步:添加序列化支持

  在本文开头我提到:Visual c++ 6.0平台中开发文档应用程序时,其文档序列化的功能非常好用,但由于其序列化能力建力在MFC之上,并不被STL支持,如何既拥有STL的效率及通用性,又保留MFC的序列化能力呢?由于篇幅的限制,我以下就只讲怎么做,而不讲为什么了(参见《MFC深入浅出》)。

  在这里我们假定map所包含的对象已具备序列化的能力,那么,对于一个map来说,其序列化实现应该如下(以Account 为例):

void SerializeMap(CArchive& ar,std::map<size,Account*>&map)
{
 typedef std::map< size,Account*>::value_type
 value_type;
 typedef std::map< size,Account*>::iterator iterator;
 if (ar.IsStoring())
 {
  DWORD n=map.size();
  ar.WriteCount(n);
  for(iterator it=map.begin();it!=map.end();++it)
  {
   ar<<it->first<<it->second;
  }
 }
 else
 {
  size first;
  Account* second;
  DWORD nNewCount=ar.ReadCount();
  while (nNewCount--)
  {
   ar>>first>>second;
   value_type value(first,second);
   map.insert(value);
  }
 }
}

  将其中的型别相关信息提取出来,利用模板技术就得到一个map的序列化支持函数如下:

template <typename Key,typename T>
void SerializeMap(CArchive& ar,std::map<Key,T>& map)
{
 typedef std::map<Key,T>::value_type value_type;
 typedef std::map<Key,T>::iterator iterator;
 if (ar.IsStoring())
 {
  DWORD n=map.size();
  ar.WriteCount(n);
  for(iterator it=map.begin();it!=map.end();++it)
  {
   ar<<it->first<<it->second;
  }
 }
 else
 {
  Key first;
  T second;
  DWORD nNewCount=ar.ReadCount();
  while (nNewCount--)
  {
   ar>>first>>second;
   value_type value(first,second);
   map.insert(value);
  }
 }
}

  这样,我们只需要在文档类的序列化函数中如下调用:

SerializeMap(ar,itsEmployees);
SerializeMap(ar,itsAccounts);
........

  即可拥有MFC内置的序列化能力了。

 
 
说明
:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,wanshiok.com不保证资料的完整性。

上一篇:VC下揭开“特洛伊木马”的隐藏面纱  下一篇:VC++实现对远程计算机屏幕的监视