• 06/19/01 11:47 PM
    Sign in to follow this  

    Reflective Factory

    General and Gameplay Programming

    Myopic Rhino
    [size="5"][b]Type[/b][/size]

    C++ Specific


    [size="5"][b]Intent[/b][/size]

    Treat 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.


    [size="5"][b]Also Known As[/b][/size]

    Class Object


    [size="5"][b]Motivation[/b][/size]

    Like 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.


    [size="5"][b]Applicability[/b][/size]

    Use the Reflective Factory pattern when
    [list][*]objects must created from classes not known until runtime, [i]and/or[/i][*]objects must have knowledge of their class and related data at runtime, [i]and/or[/i][*]more extensive type identification support is required than what is supplied by the language, [i]and/or[/i][*]the application/framework is already using several disjoint factory and/or type object mechanisms that could be integrated.[/list]
    [size="5"][b]Structure[/b][/size]

    [attachment=3500:ReflectiveFactoryStructure.jpg]


    [size="5"][b]Participants[/b][/size]
    [list][*][b]RefObject[/b][list][*]base of all objects capable of runtime instantiation and type identification via a RefClass[/list][*][b]RefClass[/b][list][*]base of all objects capable of instantiating and identifying a particular subclass of RefObject. Note that RefClass is itself a subclass of RefObject, such that classes may identify themselves as being of type class.[/list][*][b]SimpleClientObject[/b][list][*]arbitrary client object type, defined as a subclass of RefObject in order to use its services. Does not need any class-based support beyond that defined by RefClass.[/list][*][b]AdvancedClientClass[/b][list][*]client subclass of RefClass with additional class-based properties and methods.[/list][*][b]AdvancedClientObject[/b][list][*]arbitrary client object type, defined as a subclass of RefObject in order to use its services. Needs class-based support beyond that of RefClass, represented by AdvancedClientClass.[/list][/list]
    [size="5"][b]Collaborations[/b][/size]
    [list][*]RefObject and its subclasses are each represented by one instance of RefClass or one of its subclasses.[*]RefObject instances are capable of retrieving their associated RefClass instance at runtime.[*]RefClass instances are capable of creating new instances of their corresponding RefObject class at runtime.[*]RefClass instances are capable of retrieving the RefClass instance(s) of their superclass(es). RefObjects can use this mechanism for "is-a" runtime type identification.[*]RefClass subclasses may contain additional class-based information not supplied by RefClass itself. RefClasses (being RefObjects themselves) may also be "is-a" checked at runtime for the particular type of class they represent.[/list]
    [size="5"][b]Consequences[/b][/size]

    Reflective 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:
    [list][*]Abstract Factory: RefClass acts as a single-class form of the AbstractFactory role. Provided that an implementation of Abstract Factory uses factories that are only responsible for creating one class each, a RefClass would serve the same purpose.[*]Builder: RefClass subclasses can perform additional class-based functionality associated with objects of their class. This allows a form of the Builder pattern where RefClass and its subclasses serve the Builder and ConcreteBuilder roles, with the Product roles being filled by the actual classes the RefClasses represent.[*]Factory Method: The RefClass and RefObject hierarchies can almost directly serve the roles of the Creator and Product hierarchies.[*]Prototype: RefClass and RefObject can support the Prototype pattern by using a RefObject subclass hierarchy for the Prototype role hierarchy, where each object has a Clone method for cloning itself. The Prototype-capable RefObjects would be based on a RefClass subclass with the ability to create an instance of its corresponding class type that is cloned from an existing instance. The object''s Clone method merely calls the RefClass subclass's Clone method, using itself as the instance to clone from.[*]Pluggable Factories: RefClass serves a similar role to that of Maker in Culp''s Pluggable Factories variant, with the same level of extensibility (see the Related Patterns section).[/list] 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:
    [list][*]C++ Specific: While the general pattern itself is not specific to C++, its primary usefulness exists within this single language, with the sample implementation taking advantage of numerous preprocessor techniques. Some languages like Java have the functionality provided by Reflective Factory by default. Other object-oriented languages with the same RTTI constraints as C++ may still benefit from Reflective Factory, but may have to deal with increased implementation complexity if sufficient preprocessor support is not available.[*]Increased complexity with multiple inheritence: The sample implementation is restricted to single inheritence under the RefObject (and subsequently RefClass) hierarchy. While multiple inheritence support can be added, the code required for such an addition can seem rather unintuitive, and in some cases may be platform specific. Implementors who wish to add multiple inheritence support must take careful consideration of issues such as v-table pointer order, base class sharing under virtual inheritence, etc.[*]Lack of construction arguments: Since RefObjects can be created by RefClasses via a single instantiation interface, arguments to this instantiation are not allowed. Alternatives such as providing a single pointer to a parameter object or buffer, or processing of variable argument lists (via the "..." notation) do exist, but these can be rather cumbersome. A simple solution to this problem is to have RefObjects define some sort of Init() method with whatever construction parameters are required, but the client must make sure to call such a method on an object after it is created.[/list]
    [size="5"][b]Implementation[/b][/size]

    While 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.


    [size="5"][b]Sample Code[/b][/size]

    [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 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

    // 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(obj->GetClass())->GetDescription());
    }
    else
    {
    printf("Object is not an advanced client object\n");
    }

    delete obj; // virtual destructor of RefObject will delete AdvancedClientObject
    return 0;
    }[/code]
    [size="5"][b]Related Patterns[/b][/size]

    Abstract Factory, Builder, Factory Method, Prototype: Gamma et al, [i]Design Patterns - Elements of Reusable Object-Oriented Software[/i], 1st ed., Addison-Wesley 1995
    Pluggable Factories: Culp, T. "Industrial Strength Pluggable Factories", [i]C++ Report[/i], 11(10), Oct. 1999
    Type Object: Johnson, R., Woolf, B. "Type Object", Martin et al, [i]Pattern Languages of Program Design 3[/i], Addison-Wesley 1998


      Report Article
    Sign in to follow this  


    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

    There are no reviews to display.