150 likes | 387 Views
Extensible Factories. Bonus Material – Not Examinable. Contents. Interface / Factory Recap Limitations of Factory Functions Extensible Factory Functions Extensible Factories Usage. Interfaces Recap. In C++, an interface is a class that has: No member variables
E N D
Extensible Factories Bonus Material – Not Examinable
Contents • Interface / Factory Recap • Limitations of Factory Functions • Extensible Factory Functions • Extensible Factories Usage
Interfaces Recap • In C++, an interface is a class that has: • No member variables • Only pure virtual functions • Prototype with no implementation • Only defines functions & does not implement them • We cannot create objects with pure virtual functions • Inherited implementation classes provide the code • A partial implementation is a kind of abstract class • An interface class defines the functionality • The implementation classes provide the functionality for different contexts
Interface Example class IEntity // Interface { public: virtual void Update( float frameTime ) = 0; virtual void AI() = 0; } // Partial implementation – abstract class class CMovableEntity : public IEntity { public: void Update( float frameTime ){ m_Position += m_Velocity * frameTime;} virtual void AI() = 0; // Still pure virtual protected: CVector3 m_Position, m_Velocity; }
Interface Example // Complete implementation class CShipEntity : public CMovableEntity { public: void Update( float frameTime ){ m_HP += m_Damage; CMovableEntity::Update(); // Call base class Update } void AI() // (lol) { m_Velocity.x = Random(-10.0f, 10.0f); m_Velocity.z = Random(-10.0f, 10.0f); } private: int m_HP, m_Damage; }
Factory Functions • Factory functions make objects of a specific type • Typically used to create an object derived from an interface or abstract class • Without coupling to the inherited classes • A factory creates specific implementation classes • Say we use the IEntity class generally in our code • If we want to create a ship entity in our game • We create a factory function: IEntity* CreateEntity( EntityType type ); • Now we don’t need to refer to CShipEntity: IEntity* newShip = CreateEntity( ShipType );
Factory Function Example // Factory.h - Can be included anywhere – no coupling // Enumeration of types created by factory function enum { ShipType, PlanetType, }; // etc. IEntity* CreateEntity( EntityType type ); // Prototype // Factory.cpp - coupled to all types that can be created #include “CShipEntity.h” // Factory function – creates objects of a given type IEntity* CreateEntity( EntityType type ) { if (type == ShipType) { return new CShipEntity; } else // ...create other supported types }
Factory Function Example // Game code // Include factory prototype / entity types // No linkage to actual entity classes – no coupling #include “Factory.h” // ... // Create entities – no need to refer to actual classes IEntity* ship1 = CreateEntity( shipType ); IEntity* ship2 = CreateEntity( shipType ); IEntity* planet1 = CreateEntity( planetType ); // ... // Generic programming with entities ship1->Update(); // Entity class contains behaviour ship1->Render(); // etc...
Limitations of Factories • The factory function needs the definition of CShipEntity, and every other type it creates • “CShipEntity.h” must be included for the factory code • Usually means factories having their own source file • Factories are coupled to all the objects they create • So we need to recode/compile the factory every time we add a new type for it to create • Can’t add new types at runtime • Limits use for scripting / modding etc.
Extensible Factories • More general approach is an extensible factory • A factory that can create objects of any class without needing the specific class definitions • It can only create objects with the same base class • But only needs the base class definition • We must explicitly register each new class before being able to create its objects • Unregister them when not needed anymore • This can be made quite automatic in C++ • Using templates to help…
Simple Extensible Factory Class • Provide each inherited class with a “Maker” function, which can create objects of that class • Register new types with type ID and “Maker” function ptr // Factory to create any class inherited from Ientity // Note this is a factory class, not just a function class ExtensibleEntityFactory { public: // EntityMaker is a function pointer type void Register( EntityType type, EntityMaker maker ); void Unregister( EntityType type ); IEntity* Create( EntityType type );// Actual factory fn private: // Store hash map from entity types (key) to maker // functions to implement the above functions };
Simple Extensible Factory Usage • Define some types IDs: enum EntityTypes { ShipType, PlanetType }; • Define “maker” functions for our types… IEntity* ShipMaker() { return new CShipEntity; } IEntity* PlanetMaker() { return new CPlanetEntity; } • Create our factory class and register our types ExtensibleFactory EntityFactory; EntityFactory.Register( ShipType, ShipMaker ); EntityFactory.Register( PlanetType, PlanetMaker ); • Now create objects as usual IEntity* ship1 = EntityFactory.Create( ShipType );
Issues with Simple Approach • In this form we get little benefit over the original basic factory function • We must manually define a new type ID for each class • And hand code a “maker” function for each • Problems with coupling still present • We can generalise type IDs and maker functions • Use a class specific value instead of an enum for IDs • Static class member (UID or string) or perhaps use RTTI • Use a template to define a generic maker function • Assume maker functions all have same prototype
Extensible Factory Usage • ID defined in the class: class CShipEntity{ static string ID = “ShipType”; ... } • All possible maker functions defined by a single template: template <class EntityType> IEntity* EntityMaker() { return new EntityType; } • Create our factory class and register our types ExtensibleFactory EntityFactory; EntityFactory.Register( EntityMaker<CShipEntity> ); • This is simplified for brevity - see Rabin for further details and an implementation
Extensible Factory Usage • Extensible factories are advanced, but allow for much more flexible entity set-up • Simplify script-based set-up • Allow DLL usage (dynamic link library) - program add-ons can be created after the program is complete • Extensible factories can be used in other areas • An extensible resource factory is a very good idea • Allows for greater flexibility in the types of data file used • Again allowing for new data file types to be used without the need to recompile the core engine