用过C#或COM的人一定对属性化编程不会陌生,至于属性化编程的意义 ,说实话我也看得不是很明白。但是单从面向对象的抽象上看“属性”至少应该有与方法同等的地位。举例说明对于人这个抽象,我们为它定义类“People”。人都有七情六欲所以我们可以为它定义“喜”、“笑”、“怒”、“骂”等方法,当然人与人之间都不是千人一面的:头发的颜色、长短、粗细;眼睛的颜色、大小等等都是不一样的,这些都是人人都有的区别于他人的特征,是一种固有的属性。它们与跑、跳等动作是有根本区别的。如果把这些属性等同于方法,映射到程序代码上就会出现一大堆get/set方法,的确这看起来有点滑稽。其实这里最根本的错误在于混淆了对象发起的动作和与外界施加于对象上的动作-------跑是“那个人”在跑,跳是“那个人在跳”;而他留着小分头,她身材高挑都是“我看到的”。
遗憾的是c++对这层抽象不直接提供面向对象类型安全的支持。在网上看过一些人实现的属性,都是采用了类似委托的方法定义一个委托类D,并声明该委托类为某目标类的A的公开字段并在类A的构造函数中初始化该委托类:
template<class Host, class PropertyType>
class D
{
public:
typedef void (Host::*Set)(PropertyType);
typedef PropertyType (Host::*Get)();
void SetHandle(Host* object , Get get,Set set);
//其他辅助方法
};
class A
{
public:
A()
{
Age. SetHandle(this, &A::get,&A::set);
}
D<A,int> Age;
private:
void SetAge(int age);
int GetAge();
}
//!调用A的代码
A a;
a.Age = 99; /*< 哦很像C#哦!*/
很遗憾这种实现方式不可避免要对构造函数造成侵扰。经过思考我找到了一个我现阶段比较满意的方案,拿出来供大家参考批评。我实现属性的基本思路是让编译器来帮我们完成对类方法的绑定工作,通过模板参数在编译阶段把类方法地址“:&A:SetAge”、“&A::GetAge”绑定到某个自定义模板类上,我称它为属性原数据,当然原数据也是类型。源码如下:
//!类型推导辅助类,如果不这样定义在一些情况下vs2003会出现内部编译错误
template<typename Type , typename PropertyType>
struct MemberFunctionType
{
typedef PropertyType(Type::*Get)(char*);
typedef void(Type::*Set)(PropertyType,char*);
};
//!get属性原数据类模板定义
template<typename Type ,
typename PropertyType ,
typename MemberFunctionType<Type,PropertyType>::Get get>/*!<关键模板参数,在编译期推导出绑定到属性上的方法地址,在我看来方法地址是一个编译时常量。各位认同么?由于没有条件只在vs2003上测试通过*/
struct PropertyGetter
{
//!调用属性方法的静态方法。
static PropertyType get_property(Type* object, char* name)
{
assert(object);
return (object->*get)(name);
}
};
上面标红代码是关键部分,这样的实现不会造成对构造函数的侵入更重要的是对方法地址的绑定完全是在编译期完成的:
namespace Belinda
{
namespace Test
{
using namespace std;
using namespace stdext;
using namespace Belinda::Win32;
using namespace Belinda::Propertys;
class Object
{
public:
virtual string GetName(char*){return "Object";}
typedef PropertyGetter<Object , string, &Object::GetName> Name;
};
class Host:public Object
{
private:
Object _object;
int _data;
public:
void setNumber(int i , char* ){_data = i*200;}
int getNumber(char*){return _data;}
virtual string GetName(char*){return "Host";}
Object& getObject(char*){return _object;}
public:
Host(int i ):_data(i){};
public:
typedef PropertyGSetter<Host , int ,&Host::getNumber , &Host::setNumber> Number;
typedef PropertyGetter<Host, Object& , &Host::getObject> Object;
};
//typedef MemberFunctionType<Host,int, Int2Type<1> > member;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
using namespace Belinda::Test;
NOUSER_ALWAYS(argc);
NOUSER_ALWAYS(argv);
Host host(100);
cout << "Number:" << property_cast<Host::Number>(&host) << endl;
cout << "Name :" << (string)property_cast<Host::Name>(&host) << endl;
Object& object = property_cast<Host::Object>(&host);
cout << "Name :" << (string)property_cast<Object::Name>(&object) <<endl;
return 0;
}
关于性能:由于创建了一个临时对象所以肯定有性能损失,但是在我的机子上运行100000次获取属性操作和直接调用方法的性能差别不大。