I l@ve RuBoard |
![]() ![]() |
9.4 A Prototype-Based Abstract Factory ImplementationThe Prototype design pattern (Gamma et al. 1995) describes a method for creating objects starting from a prototype, an archetypal object. You obtain new objects by cloning the prototype. And the gist of it all is that the cloning function is virtual. As Chapter 8 discusses in detail, the essential problem in creating polymorphic objects is the virtual constructor dilemma: Creation from scratch needs knowledge about the type of object being created, yet polymorphism fosters not knowing the exact type. The Prototype pattern avoids the dilemma by using a prototype object. If you have one object—a prototype—you can take advantage of virtual functions. The virtual constructor dilemma still applies to the prototype itself, but it's much more localized. A prototype-based approach to building enemies for the game in our example would prescribe holding pointers to the base classes Soldier, Monster, and SuperMonster. Then we would write code like the following:[2]
class GameApp
{
...
void SelectLevel()
{
if (user chooses Diehard level)
{
protoSoldier_.reset(new BadSoldier);
protoMonster_.reset(new BadMonster);
protoSuperMonster_.reset(new BadSuperMonster);
}
else
{
protoSoldier_.reset(new SillySoldier);
protoMonster_.reset(new SillyMonster);
protoSuperMonster_.reset(new SillySuperMonster);
}
}
Soldier* MakeSoldier()
{
// Each enemy class defines a Clone virtual function
// that returns a pointer to a new object
return pProtoSoldier_->Clone();
}
... MakeMonster and MakeSuperMonster similarly defined ...
private:
// Use these prototypes to create enemies
auto_ptr<Soldier> protoSoldier_;
auto_ptr<Monster> protoMonster_;
auto_ptr<SuperMonster> protoSuperMonster_;
};
Of course, real-world code would better separate the interface and the implementation. The basic idea is that GameApp holds pointers to base enemy classes—the prototypes. GameApp uses these prototypes to create enemy objects by calling the virtual function Clone on the prototypes. A prototype-based Abstract Factory implementation would collect a pointer for each product type and use the Clone function to create new products. In a ConcreteFactory that uses prototypes, there's no longer a need to provide the concrete types. In our example, building SillySoldiers or BadSoldiers is only a matter of providing the appropriate prototypes to the factory object. The prototype's static type is the base (Soldier) class. The factory does not have to know the concrete types of the objects; it just calls the Clone virtual member function for the appropriate prototype object. This reduces the concrete factory's dependency on the concrete types. For the GenLinearHierarchy expansion mechanism to work correctly, however, there has to be a typelist. Recall ConcreteFactory's declaration: template < class AbstractFact, template <class, class> class Creator, class TList > class ConcreteFactory; TList is the concrete product list. In the EasyLevelEnemyFactory, TList was TYPELIST_3(SillySoldier, SillyMonster, SillySuperMonster). If we use the Prototype design pattern, TList becomes irrelevant. However, GenLinearHierarchy needs TList for generating one class for each product in the abstract product list. What to do? In this case, a natural solution is to pass ConcreteFactory the abstract product list as the TList argument. Now GenLinearHierarchy generates the right number of classes, and there's no need to change ConcreteFactory's implementation. ConcreteFactory's declaration now becomes template < class AbstractFact, template <class, class> class Creator, class TList = typename AbstractFact::ProductList > class ConcreteFactory; (Recall from AbstractFact's definition in Section 9.3 that it defines the inner type ProductList.) Let's now implement PrototypeFactoryUnit, the unit template that holds the prototype and calls Clone. The implementation is straightforward and is actually simpler than OpNewFactoryUnit. This is because OpNewFactoryUnit had to maintain two typelists (the abstract products and the concrete products), whereas PrototypeFactoryUnit deals only with the abstract product list. template <class ConcreteProduct, class Base> class PrototypeFactoryUnit : public Base { typedef typename Base::ProductList BaseProductList; protected; typedef typename Base::ProductList TailProductList; public; typedef typename Base::ProductList::Head AbstractProduct; PrototypeFactoryUnit(AbstractProduct* p = 0) :pPrototype_(p) {} friend void DoGetPrototype(const PrototypeFactoryUnit& me, AbstractProduct*& pPrototype) { pPrototype = me.pPrototype_; } friend void DoSetPrototype(PrototypeFactoryUnit& me, AbstractProduct* pObj) { me.pPrototype_=pObj; } template <class U> void GetPrototype(AbstractProduct*& p) { return DoGetPrototype(*this, p); } template <class U> void SetPrototype(U* pObj) { DoSetPrototype(*this, pObj); } AbstractProduct* DoCreate(Type2Type<AbstractProduct>) { assert(pPrototype_); return pPrototype_->Clone(); } private: AbstractProduct* pPrototype_; }; The PrototypeFactoryUnit class template makes some assumptions that may or may not apply to your concrete situation. First, PrototypeFactoryUnit doesn't own its prototype; sometimes, you might want SetPrototype to delete the old prototype before reassigning it. Second, PrototypeFactoryUnit uses a Clone function that supposedly clones the product. In your application you might use a different name, either because you are constrained by another library or because you prefer another naming convention. If you need to customize your prototype-based factory, you need only write a template similar to PrototypeFactoryUnit. You can inherit PrototypeFactoryUnit and override only the functions you want. For example, say you want to implement DoCreate so that it returns a null pointer if the prototype pointer is null. template <class AbstractProduct, class Base> class MyFactoryUnit : public PrototypeFactoryUnit<AbstractProduct, Base> { public: // Implement DoCreate so that it accepts a null prototype // pointer AbstractProduct* DoCreate(Type2Type<AbstractProduct>) { return pPrototype_ ? pPrototype_->Clone() : 0; } }; Let's get back to our game example. To define a concrete factory, all you have to write in the application code is the following: // Application code typedef ConcreteFactory < AbstractEnemyFactory, PrototypeFactoryUnit > EnemyFactory; To conclude, the AbstractFactory/ConcreteFactory duo offers you the following features:
Try to obtain all these benefits with a handcrafted implementation of the Abstract Factory design pattern. ![]() |
I l@ve RuBoard |
![]() ![]() |