Implementing Abstract Factory as an STL Container

The Abstract Factory design pattern provides an abstract interface for object creation. This allows applications to select object implementation at run time. Jason implements the Abstract Factory pattern using C++ and the Standard Template Library.


December 01, 1997
URL:http://www.drdobbs.com/cpp/implementing-abstract-factory-as-an-stl/184410336

Dr. Dobb's Journal December 1997: Implementing Abstract Factory as an STL Container

A practical, reusable structure

Jason is a programmer at Maxis, where he develops multiplayer games. He can be contacted at [email protected].


Sidebar: The STL Set Class

The Abstract Factory design pattern described in Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1995) provides an abstract interface for object creation. This allows applications to select object implementation at run time.

C++ templates provide a means of implementing this design pattern in a generic way, and the Standard Template Library (STL) provides an interface standard for accessing the objects created by factories. In this article, I'll examine how you can implement the Abstract Factory design pattern using C++ and STL. The Abstract Factory implementation I present provides a practical, reusable structure that preserves type safety and makes minimal demands on its object families. The STL interface ensures that the Abstract Factory will be compatible with the emerging C++ standard.

Overview of Abstract Factory

Consider the abstract class IFoo, which has the concrete implementations CFoo1 and CFoo2. Suppose you want to determine at run time which implementation of IFoo to instantiate. You can accomplish this by defining a factory class hierarchy for creating IFoo objects. This hierarchy consists of CFoo1Factory and CFoo2Factory, which both inherit IFooFactory. In turn, IFooFactory has a MakeNewObject method that returns a new IFoo object. CFoo1Factory and CFoo2Factory override this method to return new CFoo1 and CFoo2 objects, respectively.

Deciding which kind of IFoo to create at run time is a matter of selecting which kind of factory to instantiate. MakeFooFactory decides what kind of IFooFactory to make (see Listing One) subsystems needing a new IFoo don't need to know which implementation was selected.

Implementation in C++

One application of Abstract Factories is selecting platform-specific implementations of classes (such as windows and network connections) at run time.

A typical implementation in C++ of an Abstract Factory for creating window objects for Macintosh and Windows might look like Listing Two. An application using this structure detects which platform it's running on and creates the appropriate window factory. One problem is immediately evident, however. As the number of different platforms increases, so does the number of concrete factories.

In Design Patterns, Gamma and others suggest using a prototype method to address this issue. A single concrete factory is defined, and it is initialized with an instance of the object it creates. This object must have a MakeCopy routine that the factory uses to make new instances; see Listing Three. While this solves the problem of concrete factory proliferation, it also places limitations on the kinds of objects factories can create. For instance, the object must have a MakeCopy method, which may not always be possible or desirable. Also, this approach requires that the concrete factory own an actual instance of the object type it creates. If the created type has significant instantiation consequences (for example, a network class that initializes network connections or a window class that creates a visible window), it might not be desirable to have unused instances.

Templates to the Rescue

C++ templates let you apply generic programming techniques to the kinds of problems just described. They give you a set of classes with behaviors that vary only by the type of object they create.

An Abstract Factory can be implemented with templates such as those in Listing Four. New factories can now be defined more easily, as in Listings Five and Six. For any class hierarchy you create, you can now have a factory with a single typedef (as long as the concrete objects have default constructors).

The STL Interface

Automated object creation is great, but it's only half the story. It would be useful to add object storage to the IAbstractFactory<I> class to allow users access to all instances created by a given factory. As Listing Seven shows, the STL makes this easy with the set container (see the accompanying text box entitled "The STL Set Class"). The Abstract Factory now keeps track of each object created, and the STL provides an interface standard for accessing these objects.

The factory object can be designed to support the STL's nonmutating sequence operations. For this, the factory needs iterators, size, value, and reference types, and the begin/end and rbegin/rend sequences.

These requirements can be met by adding the functions and typedefs in Listing Eight to IAbstractFactory<I>. Objects created by a factory can now be easily traversed. For instance, say you wanted to have a loop that refreshed every window created by a window factory; see Listing Nine. Similarly, any of the STL algorithms that can operate on the set container, such as for_each and find_if, can be used to traverse all objects created by a factory, reducing the number of such operations you would normally have to implement.

Containing objects in this way also makes it easier to deal with issues such as object persistence and garbage collection.

Absent from the STL interface are any functions that allow users to insert or remove objects from the factory directly. The factory provides read-only access to its object list. This is appropriate because the factory owns a set of pointers to created objects. (The objects themselves are not read only -- just the pointers.)

Support for Multiple-Object Families

Another problem with Abstract Factory is that it makes it difficult to add new families of objects to the factory, since each new object type must be given a method in the Abstract Factory that must be overridden in every concrete factory.

For example, if you wanted to add scrollbars and menus to the window factories described earlier, you would have to add MakeScrollBar and MakeMenu methods to the abstract class structure.

To address this problem, Gamma suggests a MakeObject method that returns objects of different types depending on a passed parameter. This approach, however, presents a conflict between flexibility and type safety. Either every object type returned by MakeObject must inherit from a common object, which is not always desirable, or MakeObject must return a typeless pointer (void *), which the client function must cast down to the appropriate type, sacrificing type safety.

You can get around this trade-off by defining a multiobject type factory as a struct with an IAbstractFactory<I> for each object type; see Listing Ten. GUIFactory defines a factory for producing GUI elements. In Listing Eleven, which shows how GUIFactory is used, SetupGUIFactory figures out which platform the application is running on and initializes the members of GUIFactory with the appropriate concrete factory types. Adding new object families only requires modifying a single data structure, not every structure in a hierarchy. Of course, SetupGUIFactory could be a member of GUIFactory, but this tightly couples factory initialization to factory definition, which might not be appropriate.

Notes on Object Families

In practice, it is often desirable for members of object families created by factories to have private constructors and destructors and to declare their factory types as friends. This helps prevent unauthorized instantiation of objects.

In Design Patterns, Gamma and others recommend that Abstract Factories be implemented as Singletons (single-instance classes with a convenient public interface). I've found in practice that making the factories static members of the object type they create is useful. Not only does this provide a consistent standard for access to the factories, but each created object has convenient access to the factory that created it.

Conclusion

A functional implementation of the Abstract Factory template class and sample application that demonstrates the use of the Abstract Factory template with a class hierarchy for multiplayer games is available electronically (see "Availability," page 3). The sample application defines three player types (CLocalPlayer, CRemotePlayer, and CComputerPlayer) that inherit from IPlayer, and factory objects for the creation of each player type.

The main routine creates four players of each type and demonstrates iteration through each factory using the STL interface. The application compiles under both Borland C++ and Visual C++ with the RogueWave and HP implementations of the STL.


Listing One

IFooFactory *g_theFooMaker; void main() 
{ 
g_theFooMaker = MakeFooFactory(); 
IFoo *aNewFoo =  
 g_theFooMaker->MakeNewObject(); 
}


Back to Article

Listing Two

class IWindow {...}; 
class CMacWindow : public IWindow 
{...}; 
class CWindowsWindow :  
 public IWindow 
{...}; 
class IWindowFactory 
{ 
 ... 
 virtual  IWindow *MakeWindow() = 0; 
}; 
class CMacWindowFactory :  
 public IWindowFactory 
{ 
 ... 
 virtual  IWindow *MakeWindow(){ 
  return new CMacWindow; 
 } 
}; 
class CWindowsWindowFactory :  
public IWindowFactory 
{ 
 ... 
 virtual IWindow *MakeWindow(){ 
  return new CWindowsWindow; 
 }   
}; 


Back to Article

Listing Three

class CWindowFactory :   public IWindowFactory 
{ 
 ... 
 public: 
 CWindowFactory(IWindow *aWindow){ 
  m_prototype = aWindow; 
 }; 
 virtual IWindow *MakeWindow(){ 
  return m_prototype->MakeCopy(); 
 }; 
 private: 
 IWindow *m_prototype; 
}; 

Back to Article

Listing Four

template<class I> class IAbstractFactory 
{ 
 ... 
virtual I *MakeNewObject()=0; 
virtual void ReleaseObject(I *anObject) = 0;
}; 
//C inherits  from I 
template<class C,class  I> 
class CConcreteFactory :   
public IAbstractFactory<I> 
{ 
 ... 
virtual I *MakeNewObject(){ 
             return new C; 
           };
 virtual void ReleaseObject(I *anObject){ 
                delete anObject; 
              }; 
};

Back to Article

Listing Five

typedef CConcreteFactory<CWindowsWindow,IWindow> CWindowsWindowFactory;

typedef CConcreteFactory<CMacWindow,IWindow> CMacWindowFactory;

Back to Article

Listing Six

typedef  CConcreteFactory<CWinsockConnection, 
INetworkConnection>  
CWinsockFactory; 


typedef CConcreteFactory<CDirectPlayConnection, INetworkConnection> CDirectPlayFactory;

Back to Article

Listing Seven

template<class I> class IAbstractFactory 
{ 
 ... 
typedef  
set <I *,less<I *> > containerType; 
containerType m_objects; 
}; 
template<class  C,class I> 
class  CConcreteFactory  :  
public IAbstractFactory<I> 
{ 
 ... 
virtual I *MakeNewObject(){ 
 C *newObj = new C; 
 if (newObj)  
  m_objects.insert(newObj); 
 return newObj; 
 } 
virtual void ReleaseObject(I *anObject){ 
 if (m_objects.find(anObject)  
     != m_objects.end()){ 
      m_objects.erase(anObject); 
      delete anObject; 
     } 
 else 
  assert(false); 
 };
};   

Back to Article

Listing Eight

//The Iterators 

typedef containerType::iterator iterator;

typedef containerType::const_iterator const_iterator;

typedef containerType::reverse_iterator reverse_iterator;

typedef containerType::const_reverse_iterator const_reverse_iterator; //The Sequences iterator begin(){ return m_objects.begin(); } iterator end() { return m_objects.end(); } reverse_iterator rbegin(){ return m_objects.rbegin(); } reverse_iterator rend(){ return m_objects.rend(); } //Typedefs typedef containerType::size_type size_type;

typedef containerType::value_type value_type;

typedef containerType::reference reference; typedef containerType::const_reference const_reference; typedef containerType::difference_type difference_type;

Back to Article

Listing Nine

for (  IAbstractFactory<IWindow>::iterator  
 it = g_theWindowFactory->begin(); 
 it!= g_theWindowFactory->end(); 
 it++) 
{ 
 (*it)->Refresh(); 
} 

Back to Article

Listing Ten

typedef struct _tagGUIFactory { IAbstractFactory<IWindow> * 
 WindowFactory; 
IAbstractFactory<IScrollBar> * 
 ScrollBarFactory; 
IAbstractFactory<IMenu> * 
 MenuFactory; 
} GUIFactory; 

Back to Article

Listing Eleven

GUIFactory g_theGUIFactory; SetupGUIFactory(&g_theGUIFactory); 
IWindow *theMainWindow = g_theGUIFactory.WindowFactory-> 
 MakeNewObject(); 

Back to Article

DDJ


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal December 1997: The STL <i>Set </i>Class

The STL Set Class

By Jason Shankel

Dr. Dobb's Journal December 1997

set<int,less<int> > myIntSet;
for (int val=0;val<10;val++)
 myIntSet.insert(val);
for (set<int,less<int> >::iterator 
      it = myIntSet.begin();
     it!=myIntSet.end();
     it++)
 cout << *it;

Example 1: Declaring a set of integers.

Back to Sidebar


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal December 1997: The STL <i>Set </i>Class

The STL Set Class

By Jason Shankel

Dr. Dobb's Journal December 1997

set<int,less<int> > ::iterator
find_it;
//Object in set
find_it=myIntSet.find(5);
//(*find_it)==5
//Object not in set
find_it=myIntSet.find(20);
//find_it==myIntSet.end()

Example 2: If a set does not contain the object, find returns the end iterator.

Back to Sidebar


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal December 1997: The STL <i>Set </i>Class

Dr. Dobb's Journal December 1997

The STL Set Class


The STL Set class is a container that is optimized for fast lookup. Set takes two template parameters: a Key, which is the object type to be stored, and a Compare function object, which is used to sort the Keys (typically less<Key>). Set can contain only one copy of a given Key value.

The Compare object is a struct that overrides the () operator to take two references of type Key, and returns True if the first Key should appear before the second Key.

The Set class sorts its members into an ordered tree using the STL's red-black tree class, rb_tree. This structure allows fast (order logn) key searches. For an excellent explanation of red-black trees, see Mark Nelson's Programmer's Guide to the Standard Template Library (IDG Books Worldwide, 1995).

As with all STL containers, access to member objects is provided with iterators. The begin/end sequence provides access to all objects in the order defined by the Compare object.

Example 1 declares a set of integers, inserts 0-9 into the set, and streams the members out. Searching a set for a particular member is accomplished with the find method, which returns an iterator that points to the object. If a set does not contain the object, find returns the end iterator; see Example 2.

In this article, the Abstract Factory class uses set<I*, less<I*> >, where I is the template parameter to the Abstract Factory class. Since the set contains pointers and it uses the less function object on those pointers, the set is sorted in arbitrary order, based on the numerical values of the pointers the factory allocates.

If object order is important, you may want to replace the compare function in the set container from less<I*> with something that sorts appropriately for your application.

-- J.S.


Copyright © 1997, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.