I l@ve RuBoard |
![]() ![]() |
9.3 Implementing AbstractFactoryNow that we have defined the interface, we should look into ways to make implementation as easy as possible. Given the use of typelists in defining the interface, a natural way of building a generic implementation of AbstractFactory would be to employ typelists of concrete products. In practical terms, building the Easy level concrete factory should be as simple as the following: // Application code typedef ConcreteFactory < // The abstract factory to implement AbstractEnemyFactory, // The policy for creating objects // (for instance, use the new operator) OpNewFactoryUnit, // The concrete classes that this factory creates TYPELIST_3(SillySoldier, SillyMonster, SillySuperMonster) > EasyLevelEnemyFactory; The three arguments to the (now hypothetical) ConcreteFactory class template are enough information to implement a complete factory:
Now that we have established the synopsis of ConcreteFactory, let's figure out how we can implement it. Simple math leads us to the conclusion that there should be as many pure virtual function overrides as there are definitions. (Otherwise we wouldn't be able to instantiate ConcreteFactory, and by definition, ConcreteFactory must be ready to serve.) Consequently, ConcreteFactory should inherit OpNewFactoryUnit (which is responsible for implementing DoCreate) instantiated with every type in the typelist. Here the GenLinearHierarchy class template, which complements GenScatterHierarchy (see Chapter 3), can be of great use because it takes care of all the details of generating the instantiations for us. AbstractEnemyFactory must be the root of the hierarchy. All DoCreate implementations and ultimately EasyLevelEnemyFactory must derive from it. Each instantiation of OpNewFactoryUnit overrides one of the three DoCreate pure virtual functions defined by AbstractEnemyFactory. Let's proceed by defining OpNewFactoryUnit. Obviously, OpNewFactoryUnit is a class template having the type to create as a template parameter. In addition, GenLinearHierarchy requires that OpNewFactoryUnit accept an additional template parameter and derive from it. (GenLinearHierarchy uses this second parameter to generate the string-shaped inheritance hierarchy shown in Figure 3.6.) template <class ConcreteProduct, class Base> class OpNewFactoryUnit : public Base { typedef typename Base::ProductList BaseProductList; protected: typedef typename BaseProductList::Tail ProductList; public: typedef typename BaseProductList::Head AbstractProduct; ConcreteProduct* DoCreate(Type2Type<AbstractProduct>) { return new ConcreteProduct; } }; OpNewFactoryUnit must do only some type calculations for figuring out which abstract product to implement. Each OpNewFactoryUnit instantiation is a component in a food chain. Each OpNewFactoryUnit instantiation "eats" the head of the product list by overriding the appropriate DoCreate function, and passes the beheaded ProductList down the class hierarchy. Thus, the topmost OpNewFactoryUnit instantiation (the one just below AbstractEnemyFactory) implements DoCreate(Type2Type<Soldier>), and the bottommost OpNewFactoryUnit instantiation implements DoCreate(Type2Type<SuperMonster>). Let's recap how OpNewFactoryUnit honors its position in the food chain. First, OpNewFactoryUnit imports the ProductList type from its base class and renames it BaseProductList. (If you look at AbstractFactory's definition, you'll see that indeed it exports the ProductList type.) The abstract product that OpNewFactoryUnit implements is the head of BaseProductList, hence AbstractProduct's definition. Finally, OpNewFactoryUnit reexports BaseProductList::Tail as ProductList. This way the remaining list is passed down the inheritance hierarchy. Notice that OpNewFactoryUnit::DoCreate does not return a pointer to AbstractProduct, as its pure counterpart does. Instead, OpNewFactoryUnit::DoCreate returns a pointer to a ConcreteProduct object. Does it still qualify as an implementation of the pure virtual function? The answer is yes, thanks to a C++ language feature called covariant return types. C++ allows you to override a function that returns a pointer to a base class with a function that returns a pointer to a derived class. It makes a lot of sense. With covariant return types, you either know the exact type of the concrete factory and you get maximum type information, or you know only the base type of the factory and you get lesser type information. ConcreteFactory must generate a hierarchy using GenLinearHierarchy. Its implementation is straightforward: template < class AbstractFact, template <class, class> class Creator = OPNewFactoryUnit class TList = typename AbstractFact::ProductList > class ConcreteFactory : public GenLinearHierarchy< typename TL::Reverse<TList>::Result, Creator, AbstractFact> { public: typedef typename AbstractFact::ProductList ProductList; typedef TList ConcreteProductList; }; The class hierarchy that GenLinearHierarchy generates for ConcreteFactory is shown in Figure 9.3. Figure 9.3. The class hierarchy generated for EasyLevelEnemyFactoryThere is only one twist: ConcreteFactory must reverse the concrete product list when passing it to GenLinearHierarchy. Why? Well, we have to go back to Figure 3.6, which shows how GenLinearHierarchy generates a hierarchy. GenLinearHierarchy distributes types in the typelist to its Unit template argument from the bottom up; the first element in the typelist is passed to the Unit instantiation that's at the bottom of the class hierarchy. However, OpNewFactoryUnit implements the DoCreate overloads in a top-down fashion. In conclusion, ConcreteFactory reverses the typelist TList using the TL::Reverse compile-time algorithm (see Chapter 3) before passing it to GenLinearHierarchy. If, at this point, you still find AbstractFactory and ConcreteFactory a bit confusing or too complicated, take heart. It is because the two class templates make nonchalant use of typelists. Typelists themselves are likely a new concept to you, and it will take some time to get used to them. If you think of typelists as a black-box concept—"typelists are to types what regular lists are to values"—the implementations in this chapter are very simple. And once you truly get used to typelists, the sky's the limit. Unconvinced? Read on. |
I l@ve RuBoard |
![]() ![]() |