Reflective Factory
TypeC++ Specific IntentTreat the class of an object as its own object, serving as both a factory for runtime instantiation and a mechanism for simple reflection services like runtime type identification. Also Known AsClass Object MotivationLike many applications, games often have a wide variety of object types that may need to be constructed at runtime without knowledge of the underlying concrete classes involved. Object creational patterns such as Abstract Factory, Prototype, Pluggable Factories, etc. have been designed as some of the possible solutions to this problem. Other patterns such as Type Object represent the type of an object as an object, but one which is not necessarily directly bound to the underlying class and which hence serves a supplemental role in type identification. To simplify the architecture of an application or framework, it may be desirable to merge the functionality of type objects and factories into a combined mechanism. While sophisticated virtual construction and reflection services of this sort exist in languages like Java, they are not present in C++ beyond extremely primitive RTTI support (using dynamic_cast, type_info, etc). Reflective Factory does not completely bridge this gap, but it is a step in the right direction, and it is simple to implement. ApplicabilityUse the Reflective Factory pattern when
StructureParticipants
Collaborations
ConsequencesReflective Factory simplifies factory-based instantiation and runtime type identification services into a simple unified mechanism. The pattern is easy to implement, and offers advantages over several alternatives by supplying the functionality of multiple object creational patterns simultaneously:
In addition, Reflective Factory provides runtime type information beyond merely that of a class''s name or ID, leading to possible extension into a full reflection API if a means of supplying property and method information were provided. Such information could be supplied manually by the application, generated automatically via a preprocessing tool, supplied by a supplemental definition from a script language or IDL, determined via platform-specific analysis of debug symbol tables, etc. Reflective Factory does have a few disadvantages worthy of mention:
ImplementationWhile there are a variety of ways to implement this pattern, the nature of its use primarily within C++ implies that a C++ example should be used to describe the reference implementation. See Sample Code section below. Note the heavy use of preprocessor macros to supply the necessary functionality in a manner that is convenient for client use. The preprocessor was used instead of template classes for this, due to the "stringizing" # operator necessary for identifying the string name of a class. If a future standard of C++ adds in stringizing support for template parameters, the implementation could be modified to use templates more, thus causing less preprocessor abuse. Note also the use of a single-linked list of all classes in the system, built up as classes initialize themselves. The use of this simple list is good for example purposes, although applications needing more advanced "FindClassByName" capabilities should use a data structure that is more effective in terms of search speed. Since classes do not need to remove themselves from this search mechanism once they are inserted, removal time is not a factor when considering possible data structures and algorithms. In practice, a simple hash table (hashing the class by name) or binary tree is sufficient for most applications. Other options involve using standard container classes, like those provided by the STL. Finally, observe the use of a pair of static methods in the RefClass sample code for retrieving static pointers to the common class linked list head and count. These static values are wrapped around by static methods in order to resolve the Static Initialization Order problem. If such wrappers were not used, the linked list head and count would be static members of RefClass itself, and arbitrary initialization order (as determined by the compiler) could cause these to be initialized after some RefClass instances had been initialized, causing corruption of the list. Whether an implementation of the pattern uses a single-linked list or another data structure for its class list, the Static Initialization Order problem must be taken into account, not only for the class list, but for the implementation as a whole. Sample Code//--------------------------------------------- // REFFACT.H //--------------------------------------------- /* Internal macros These internal macros are used by the REF_CLASS_DEFINE and REF_CLASS_DEFINE_ABSTRACT macros, and need not be used directly by clients. RefObject itself is the only class which uses any of these directly, since it has a special form of StaticGetClass() that compensates for a null superclass. */ // reflective object class base definition, not used directly by clients #define REF_CLASS_DEFINE_BASE(xName, xClassType) \ private: \ static xClassType* sClass; \ public: \ typedef xName ThisClass; \ typedef xClassType ThisClassClass; \ \ virtual RefClass* GetClass() \ { \ return StaticGetClass(); \ } // reflective object class definition without a new, not used directly by clients #define REF_CLASS_DEFINE_NEEDNEW(xName, xSuper, xClassType) \ REF_CLASS_DEFINE_BASE(xName, xClassType); \ typedef xSuper Super; \ static xClassType* StaticGetClass() \ { \ if (!sClass) \ { \ sClass = xClassType::StaticNew(); \ sClass->InitClass(#xName, xSuper::StaticGetClass(), (RefObject*(*)())xName##::StaticNew); \ StaticInitClass(); \ } \ return sClass; \ } \ static void StaticInitClass() // reflective object class definition abstract new, not used directly by clients #define REF_CLASS_DEFINE_ABSTRACTNEW(xName) \ private: \ static xName* StaticNew() { return 0; } // reflective object class definition concrete new, not used directly by clients #define REF_CLASS_DEFINE_CONCRETENEW(xName) \ public: \ static xName* StaticNew() { return new xName; } /* REF_CLASS_DEFINE This macro is used in the class definition of any concrete class derived from RefObject. Example: class MyObject : public RefObject { public: REF_CLASS_DEFINE(MyObject, RefObject, RefClass); void DoSomething(); // add methods/properties here as usual }; */ #define REF_CLASS_DEFINE(xName, xSuper, xClassType) \ REF_CLASS_DEFINE_CONCRETENEW(xName); \ REF_CLASS_DEFINE_NEEDNEW(xName, xSuper, xClassType) /* REF_CLASS_DEFINE_ABSTRACT Same as REF_CLASS_DEFINE, but for abstract classes which cannot be instantiated. This is necessary for classes with unimplemented virtual members, since attempting to use "new" on such classes results in a compiler error. */ #define REF_CLASS_DEFINE_ABSTRACT(xName, xSuper, xClassType) \ REF_CLASS_DEFINE_ABSTRACTNEW(xName); \ REF_CLASS_DEFINE_NEEDNEW(xName, xSuper, xClassType) /* REF_CLASS_IMPLEMENT This macro is used in the implementation source of a class (such as a backing .cpp file), in order to hold and initialize the static class member of a RefObject subclass. */ #define REF_CLASS_IMPLEMENT(xName) \ xName##::ThisClassClass* xName##::sClass = xName##::StaticGetClass(); /* Class Prototypes */ class RefObject; class RefClass; /* RefObject */ class RefObject { protected: RefObject() {} // empty constructor virtual ~RefObject() {} // virtual destructor (must be virtual for "delete" to work properly) REF_CLASS_DEFINE_BASE(RefObject, RefClass); REF_CLASS_DEFINE_ABSTRACTNEW(RefObject); public: // custom variant of StaticGetClass from macro, without a superclass static RefClass* StaticGetClass(); // returns whether this object "is-a" given class of object virtual bool IsA(RefClass* inClass); }; /* RefClass */ class RefClass : public RefObject { protected: const char* mClassName; // name of the class RefClass* mClassSuper; // superclass object for class RefClass* mClassNext; // next class in a linked list of all classes RefObject* (*mClassNewFunc)(); // StaticNew() function from class used for instantiation // get pointer to first class, used by InitClass() only static RefClass** StaticGetFirstClassPtr() { static RefClass* sClassList = 0; return &sClassList; } // get pointer to count of classes, used by InitClass() only static unsigned long* StaticGetClassCountPtr() { static unsigned long sClassListCount = 0; return &sClassListCount; } public: REF_CLASS_DEFINE(RefClass, RefObject, RefClass) {} // get first class in linked list of classes inline static RefClass* StaticGetFirstClass() { return *StaticGetFirstClassPtr(); } // get number of classes in class list inline static unsigned long StaticGetClassCount() { return *StaticGetClassCountPtr(); } // return name of class inline const char* GetName() { return mClassName; } // return superclass object for class inline RefClass* GetSuper() { return mClassSuper; } // return next class in linked list of all classes inline RefClass* GetNextClass() { return mClassNext; } // instantiate an object of this class inline RefObject* New() { return mClassNewFunc(); } // returns whether this class is derived from (or is the same as) a given class virtual bool IsDerivedFrom(RefClass* inClass); // initialize class, called by definition macros after class is created virtual void InitClass(const char* inName, RefClass* inSuper, RefObject* (*inNewFunc)()); }; // convenience template casting function, same syntax as dynamic_cast template <class T> T* REF_Cast(RefObject* inObj) { return( (inObj && inObj->IsA(T::StaticGetClass())) ? (T*)inObj : 0 ); } //--------------------------------------------- // REFFACT.CPP //--------------------------------------------- // implementation of RefObject and RefClass REF_CLASS_IMPLEMENT(RefObject); REF_CLASS_IMPLEMENT(RefClass); // custom StaticGetClass for RefObject, has a null superclass RefClass* RefObject::StaticGetClass() { if (!sClass) { sClass = RefClass::StaticNew(); sClass->InitClass("RefObject", 0, RefObject::StaticNew); } return sClass; } // returns whether an object "is-a" given class of object; just punts to IsDerivedFrom() in class bool RefObject::IsA(RefClass* inClass) { return GetClass()->IsDerivedFrom(inClass); } // returns whether a class is derived from (or is the same as) a given class, walks class chain bool RefClass::IsDerivedFrom(RefClass* inClass) { if (!inClass) return false; for (RefClass* cls = this; cls; cls = cls->GetSuper()) { if (cls == inClass) return true; } return false; } // initialize class, adds to class list void RefClass::InitClass(const char* inName, RefClass* inSuper, RefObject* (*inNewFunc)()) { // set applicable class properties mClassName = inName; mClassSuper = inSuper; mClassNewFunc = inNewFunc; mClassNext = StaticGetFirstClass(); // set this as new first class in class list chain *StaticGetFirstClassPtr() = this; // increase the class count (could have used ++ here, but wanted to prevent reader confusion) *StaticGetClassCountPtr() = *StaticGetClassCountPtr() + 1; } //--------------------------------------------- // TEST.CPP //--------------------------------------------- // simple client object example, derives from RefObject and uses a RefClass directly class SimpleClientObject : public RefObject { public: REF_CLASS_DEFINE(SimpleClientObject, RefObject, RefClass) {} }; // example subclass of RefClass which adds additional class-based functionality class AdvancedClientClass : public RefClass { protected: const char* mDescription; public: REF_CLASS_DEFINE(AdvancedClientClass, RefClass, RefClass) {} void SetDescription(const char* inDescription) { mDescription = inDescription; } const char* GetDescription() { return mDescription; } }; // example subclass of refobject which uses the RefClass subclass instead of RefClass itself class AdvancedClientObject : public RefObject { public: REF_CLASS_DEFINE(AdvancedClientObject, RefObject, AdvancedClientClass) { // note the use of the initialization function body for the first time StaticGetClass()->SetDescription("Advanced client class for some client object"); } virtual void DoSomethingAdvanced() {} }; REF_CLASS_IMPLEMENT(SimpleClientObject); REF_CLASS_IMPLEMENT(AdvancedClientClass); REF_CLASS_IMPLEMENT(AdvancedClientObject); #include <stdio.h> // example main function to demonstrate test classes int main() { // get number of classes in system printf("%d classes\n", RefClass::StaticGetClassCount()); // print out a list of all classes and their superclasses for (RefClass* cls = RefClass::StaticGetFirstClass(); cls; cls = cls->GetNextClass()) printf("%s -> %s\n", cls->GetName(), cls->GetSuper() ? cls->GetSuper()->GetName() : "(null)"); // create a new object RefObject* obj = new AdvancedClientObject; // same as AdvancedClientClass::StaticGetClass()->New(); // test if this object is of a desired class, if it is, perform custom functionality if (obj->IsA(AdvancedClientObject::StaticGetClass())) { printf("Object is an advanced client object\n"); printf("Class description is \"%s\"\n", REF_Cast<AdvancedClientClass>(obj->GetClass())->GetDescription()); } else { printf("Object is not an advanced client object\n"); } delete obj; // virtual destructor of RefObject will delete AdvancedClientObject return 0; } Related PatternsAbstract Factory, Builder, Factory Method, Prototype: Gamma et al, Design Patterns - Elements of Reusable Object-Oriented Software, 1st ed., Addison-Wesley 1995
Discuss this article in the forums
See Also: © 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
|