引子:网上对MFC的批评不绝于耳,说MFC这样,那样不好,甚至有网友直接用“烂”来形容MFC。我学习MF C也有一段时间了,我自己感觉我的MFC水平也就是中等偏下,离熟练运用MFC还有一段距离。我深刻体会到学习MFC的“痛苦”,所以在工作中我一般都尽量避免使用MFC。前几天在CSDN中看到关于MFC中用到了那些设计模式的讨论,顿时觉得这是一个很有意义的讨论。为什么我们不可以换一个角度,从实现者的角度来分析和学习MFC?MFC作为一个庞大的应用程序框架(Application Framework),里面蕴藏了大量优秀的设计思想和方法,如果将这些优秀的设计思想和方法能够灵活运用到我们自己的代码,我们的设计和代码质量就可以上升到另一个层次,我们自己也上升了一个档次。要分析MFC中运用的设计模式,很显然这两个方面都要很熟。设计模式和MFC都是一个很庞大的体系,任何一个的学习都是一个漫长和痛苦的过程,所以我觉得要想全面的分析几乎是不可能的(牛人除外),所以这里的分析只局限于我自己有限的知识,但是我相信随着学习的深入以及我自身水平的提高,分析会变得更加全面。
厂(Factory)顾名思义,就是制造东西的东西。具体到软件开发上就是指用来生成对象的“东西”。这里的“东西”可能是一个方法(例如全局函数),也可以是一个类(COM中的类厂)。简单来说,运用厂的目的是隐藏对象具体的创建过程,客户代码提供创建对象所需要的材料,然后就从厂那里拿到已经创建好的对象,而不用关心对象是怎样创建的。厂的思想虽然简单,简单的几乎使我们忽视了它的存在,但是它却频繁的出现在代码中,成为使用最频繁的设计思想。
最简单的厂就是一个函数,这个函数只创建一种对象。例如以下的代码:
class Football;
Football* CreateFootball();
从函数名CreateFootball就可以看出,这个函数用来创建一个Football对象。当然我们可以给函数添加一些参数用来指定创建的对象的属性。这个函数(或者说类似的函数)有个致命的问题,就是功能过于单一和死板,如果我们有N种对象,就需要写N个相应的函数来创建他们。有没有更好的办法?
(下面终于到了MFC出场了,鼓掌!)
(MFC:今天非常激动!谢谢Microsoft, 谢谢OO,谢谢大家对我的关心。。。)
MFC仅用一个方法就可以创建所有从CObject派生的对象。这个方法就是CRuntimeClass::CreateObject()。MFC做了些什么使CRuntimeClass::CreateObject()如此神奇?这里的秘密就在于CRuntimeClass类(更确切的说,CRuntimeClass是一个结构体)。我们在构造对象的时候,首先要知道对象占用多少内存空间,还需要一个构造函数去初始化构造的对象。在CRuntimeClass中正好有两个成员变量涉及到这两个方面的内容:
m_nObjectSize - The size of the object, in bytes
根据它的值,创建对象的时候就知道分配到少的空间
m_pfnCreateObject - A function pointer to the default constructor that creates an object of your class
这是一个指向对象默认构造函数的函数指针,通过它在创建对象的时候可以调用默认的构造函数初始化对象
接下来需要解决的问题就是如何将CRuntimeClass和我所要创建的对象联系起来,即让CRuntimeClass知道要创建的是什么东西。这里MFC是通过两个宏(DECLARE_DYNCREATE/IMPLEMENT_DYNCREATE)来建立这个联系的。通过这两个宏,任何从CObject派生的类都会有一个相应的CRuntimeClass对象,这个CRuntimeClass对象储存了要创建的对象的一些信息,比如上面提到的对象的大小,构造函数指针等等。
在接下来的问题就是如何在运行时能拿到CRuntimeClass对象,然后通过它来创建我需要的对象。这里就需要使用MFC中的运行时类型识别(RTTI)的支持。MFC使用DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏来实现RTTI。简单的说,每一个类相应的CRuntimeClass对象(静态对象)会被添加到一个静态的,全局的CRuntimeClass类型的链表上,以后通过查找这个链表,就可以找到每个类对象的CRuntimeClass对象。
对于CRuntimeClass::CreateObject(),其实也是每一个对象对应一个厂函数,只不过是不需要我们手工的添加这些厂函数,MFC用更巧妙的方式实现了。CRuntimeClass还提供了一个静态函数:static CObject* PASCAL CreateObject(LPCSTR lpszClassName),这个函数仅仅需要类名就可以创建相应的对象,真正做到了一个函数创建N种对象(有点像机器猫的百宝箱:))。实现的机理和上面的是一致的,这里就不多说了。
我只是从原理上对MFC中的对象动态创建进行了分析,具体的实现细节都被隐藏的神秘的宏后面的。如果大家对实现的细节也感兴趣的话,可以将宏展开后分析,或者参阅其他的书籍。
通过以上对MFC中对象动态创建的分析,如果我们想让一个函数可以创建多种对象的能力,函数原型一般具有以下的特征:
1。函数返回值。厂函数的返回值一般都是基类指针,用户在使用这个指针之前,需要将这个指针转换到正确的类型指针。例如以下代码:
class CShape {...};
class CRectange : public CShape {...};
CShape* createRectange() {
return new CRectange();
}
CRectange* pRect = static_cast<CRectange*>(createRectange()); //explicitly casting
可以看出,这就要求所有的类都从一个基类派生,就像MFC中几乎所有的类都有一个共同的基类CObject
2。对于函数的参数,没有特别的要求。函数的参数可以用来区分要创建的对象类型,例如以下的代码:
class CShape {...};
class CRectange : public CShape {...};
class CCircle : public CShape {...};
CShape* createShape(enum shapeType) {
if(shapeType == RECTANGE) return new CRectange();
if(shapeType == CIRCLE) return new CCircle();
}
在MFC中也有一个这样的例子,就是COleObjectFactory。根据类名就可以猜测出,COleObjectFactory是一个用来创建OLE object的类厂。COleObjectFactory的构造函数:
COleObjectFactory(REFCLSID clsid,CRuntimeClass* pRuntimeClass,BOOL bMultiInstance,LPCTSTR lpszProgID );
其中的前两个参数就决定了要创建的对象的类型。
然后,通过调用COleObjectFactory::OnCreateObject()来返回创建的对象。
参考文献:
1.MSDN
2."Different ways of implementing factories" - http://www.codeproject.com/cpp/all_kinds_of_factories.asp
3."Object Creation: Implementing Factory Patterns in C++" - http://www.raba.com/~jcstaff/oodev/presents/objcreat/
未完待续。。。