I l@ve RuBoard Previous Section Next Section

9.2 A Generic Abstract Factory Interface

As hinted in Chapter 3, the Typelist facility makes implementing generic Abstract Factories a slam-dunk. This section describes how to define a generic AbstractFactory interface with the help of typelists.

The example shown in the previous section is a typical use of the Abstract Factory design pattern. To recap:

  • You define an abstract class (the abstract factory class) that has one pure virtual function for each product type. The virtual function corresponding to type T usually returns a T*, and its name is CreateT, MakeT, or something similar.

  • You define one or more concrete factories that implement the interface defined by the abstract factory. You implement each member function by creating a new object of a derived type, usually by invoking the new operator.

All this seems simple enough, but as the number of products created by the abstract factory grows, the code becomes less and less maintainable. Furthermore, at any moment you might decide to plug in an implementation that uses a different allocation, or a prototype object.

A generic Abstract Factory would be of help here, but only if it's flexible enough to easily accommodate things such as custom allocators and passing arguments to constructors.

Recall the class template GenScatterHierarchy from Chapter 3. GenScatterHierarchy instantiates a basic template—provided by the user—with each type in a typelist. By its structure, the resulting instantiation of GenScatterHierarchy inherits all the instantiations of the user-provided template. In other words, if you have a template Unit and a typelist TList, GenScatterHierarchy<TList, Unit> is a class that inherits Unit<T> for each type T in TList.

GenScatterHierarchy can be very useful for defining an abstract factory interface—you define an interface that can create objects of one type, and then you apply that interface to multiple types with GenScatterHierarchy.

The "unit" interface, which can create objects of a generic type T, is as follows.



template <class T>


class AbstractFactoryUnit


{


public:


   virtual T* DoCreate(Type2Type<T>) = 0;


   virtual ~AbstractFactoryUnit() {}


};


This little template looks perfectly kosher—virtual destructor and all[1]—but what's that Type2Type business? Recall from Chapter 2 that Type2Type is a simple template whose unique purpose is to disambiguate overloaded functions. Okay, but then where are those ambiguous functions? AbstractFactoryUnit defines only one DoCreate function. There will be several AbstractFactoryUnit instantiations in the same inheritance hierarchy, as you'll see in a moment. The Type2Type<T> helps in disambiguating the various DoCreate overloads generated.

[1] See Chapter 4 for an extended discussion of virtual destructors.

The generic AbstractFactory interface uses GenScatterHierarchy in conjunction with AbstactFactoryUnit, as follows:



template


<


   class TList,


   template <class> class Unit = AbstractFactoryUnit


>


class AbstractFactory : public GenScatterHierarchy<TList, Unit>


{


public:


   typedef TList ProductList;


   template <class T> T* Create()


   {


      Unit <T>& unit = *this;


      return unit.DoCreate(Type2Type<T>());


   }


};


Aha, there's Type2Type in action to make it clear which DoCreate function is called by Create. Let's analyze what happens when you type the following:



// Application code


typedef AbstractFactory


<


   TYPELIST_3(Soldier, Monster, SuperMonster)


>


AbstractEnemyFactory;


As Chapter 3 explains, the AbstractFactory template generates the hierarchy depicted in Figure 9.2. AbstractEnemyFactory inherits AbstractFactoryUnit<Soldier>, AbstractFactoryUnit<Monster>, and AbstractFactoryUnit<SuperMonster>. Each defines one pure virtual member function Create, so AbstractEnemyFactory has three Create overloads. In a nutshell, AbstractEnemyFactory is pretty much equivalent to the abstract class of the same name defined in the previous section.

Figure 9.2. The class hierarchy generated for AbstractEnemyFactory

graphics/09fig02.gif

The template member function Create of AbstractFactory is a dispatcher that routes the creation request to the appropriate base class:



AbstractEnemyFactory* p = ...;


Monster* pOgre = p->Create<Monster>();


One important advantage of the automatically generated version is that AbstractEnemyFactory is a highly granular interface. You can automatically convert a reference to an AbstractEnemyFactory to a reference to an AbstractFactory<Soldier>, AbstractFactory <Monster>, or AbstractFactory<SuperMonster>. This way, you can pass only small subunits of the factory to various parts of your application. For instance, a certain module (Surprises.cpp) needs only to create SuperMonsters. You can communicate to that module in terms of pointers or references to AbstractFactory<SuperMonster>, so that Surprises. cpp is not coupled with Soldier and Monster.

Using AbstractFactory's granularity, you can reduce the coupling that affects the Abstract Factory design pattern. You gain this decoupling without sacrificing the safety of your abstract factory interface because only the interface, not the implementation, is granular.

This brings us to the implementation of the interface. The second important advantage of the automatically generated AbstractEnemyFactory is that you can automate the implementation, too.

    I l@ve RuBoard Previous Section Next Section