Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Implementing Abstract Factory as an STL Container


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(); 
}


</p>

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; 
 }   
}; 


</p>

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; 


</p>
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 

</p>
typedef  
containerType::iterator  
iterator; 


</p>
typedef  
containerType::const_iterator  
const_iterator; 


</p>
typedef  
containerType::reverse_iterator  
reverse_iterator; 


</p>
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; 


</p>
typedef  
containerType::value_type  
value_type; 


</p>
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


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.