7/24/08

MFC - Microsoft Foundation Classes Design Patterns

1 Introduction

This paper describes the use of object-oriented software design patterns, as presented in Design Patterns: Elements of Reusable Object-Oriented Software by Gamma et al., within the Microsoft Foundation Class Library (MFC). MFC is used for implementing applications for Microsoft Windows operating systems.

Because of the size of the MFC library, a complete analysis would have been beyond the scope of this assignment. Instead, we identified various possible locations for design patterns, using the class hierachy diagram of MFC, and studied the source carefully at these locations. When we did not find a pattern where we expected one, we have documented it anyway, with examples of how the particular problem could have been solved differently, perhaps more elegantly, using design patterns.

We have included a brief introduction to MFC in Section 2, as background information. The analysis has been split into three parts, with one section for each major design pattern category:

2 Microsoft Foundation Classes Overview

The Microsoft Foundation Class Library (MFC) is a key component of Microsoft's professional application development system. The other component is the Visual C++ integrated development environment and tools. These are both implemented on top of the Win32 API, and the Visual C++ Developer Studio is implemented on top of MFC itself. The Win32 API is available on the Windows 95 and Windows NT operating systems.

MFC provides a C++ application framework class library. Visual C++ contains high-level tools for creating MFC applications, such as Component Gallery, ClassWizard, and AppWizard. The original version 1.0 of MFC was released in April 1992 as a 16-bit version. The current version is 4.2, which is only available as a 32-bit version.

2.1 Class Overview

MFC contains well over 200 classes. These can be divided into several categories. A number of classes are involved in creating a graphical application user interface, such as frames, views, menus, dialogs and dialog controls. Other categories include graphics drawing classes, file and socket classes, database access classes, thread support and synchronization classes, OLE (Object Linking and Embedding) support classes, Internet support classes, collection classes such as arrays, lists, and maps, and a large number of other support classes.

2.2 The CObject Base Class

Most MFC classes are subclasses of CObject. This class provides a number of important services. The first of these is object creation with a CRuntimeClass object. An instance of CRuntimeClass contains the method CreateObject() that uses a function pointer data member to create an instance of some CObject subclass that supports dynamic creation. The CObject subclasses contain a CRuntimeClass reference, so that the exact subclass of any object can be determined at run time.

Another importart concept supported by CObject is serialization, the ability to store and retrieve instances of the class to and from external storage, such as files. This can be used to implement object persistence, OLE, and other useful mechanisms. Also, non-homogenous, type-safe collections of CObject subclasses are supported.

2.3 The Document/View Architecture

All graphical MFC applications have to be based on MFC's document/view structure. The application defines one or more document templates (CDocTemplate subclass) that contain CRuntimeClass references the following three classes:

  • a document class (CDocument subclass),
  • a frame class (CFrameWnd subclass),
  • a view class (CView subclass).

The document class contains the internal representation of the application data. An instance is created by the framework for each new or opened document. The frame class describes the user interface of document windows of the application, typically multiple-document interface (MDI) frames. The view class shows a graphical representation of the document type. All classes as CObject subclasses.

A number of overridable methods in each of these classes allow the application to represent almost any kind of document. This basic structure is supported by a large number of support services, such as transparent printing and print preview support and OLE support.

2.4 Graphics Drawing Support

The graphics drawing support classes are modeled on the Win32 Graphics Device Interface (GDI). A number of convenience classes as well as overloaded constructors provide more flexible tools for drawing than the Win32 API, although the basic concepts are the same in MFC. Allocating and deallocating GDI resources and other error-prone tasks are simplified as the standard overridable methods in the CView class provide a default structure for implementing drawing accepting user input in a graphical application.

2.5 Dialog Support

MFC contains numerous classes for creating forms and other dialogs. The standard Windows controls are supported as well as the standard Windows common dialogs. Also, the Windows 95 new common controls and common dialogs are supported. MFC defines a standard dialog data exchange and dialog data validation (DDX/DDV) mechanism that provides a default structure for manipulating dialog data entered by the user. The record set and record view classes work with the database access classes, simplifying the display and editing of database rows presented on forms.

2.6 Other User Interface Features

The menu, toolbar, tabbed dialog, and status bar classes implement the latest Windows look and feel. The visual editing server and container classes, automation server and client classes, and other OLE support classes help implement object linking and embedding, the Windows standard data exchange between applications. The basic structure for an OLE support is present in these and the document/view classes, but a lot of work is left for the application code.

3 Creational Patterns in MFC

Creational patterns find a number of uses in MFC. The standard method of creating objects is almost always direct instatiation of MFC classes or their application-specific subclasses. When patterns are required, the CObject-supported dynamic creation is most often used. This is equivalent to having classes as first-class objects in a language, which makes comparing the patterns to C++ pattern examples complicated.

Some situations take advantage of mechanisms similar to the abstract factory and builder patterns. The abstract factory pattern is used for creating OLE (Object Linking and Embedding) objects, which is a fairly specialized case. The builder pattern is used with the fundamental document/view structure of all MFC applications. Neither of the examples discussed here is a very typical pattern use; however, the patterns are recognizable.

3.1 Abstract Factory

The abstract factory pattern is used within the COleObjectFactory class for creating OLE objects of unknown types, each a subclass of CCmdTarget. The client initializes the factory with an OLE class id, which is used to select and store the object type created by this factory, represented by a CRuntimeClass object. Each an instance of the the factory class can only create a single type of object.

The COleObjectFactory class contains a method OnCreateObject(), which is called by the MFC framework at some point after the class has been instantiated. This method invokes the CreateInstance() method of the CRuntimeClass member and returns a CCmdTarget type object pointer.

The following figures illustrate the similarities and differences of the abstract factory and COleObjectFactory.

Figure 3.1: The standard abstract factory structure.

Figure 3.2: The COleObjectFactory structure.

This structure does not require the ConcreteFactory subclasses to be derived. Instead, the dynamic creation feature of CObject subclasses is used. Also, the family of products created contains only one member in this case. However, Client has a way of parameterizing which ConcreteFactory to use and it always receives an AbstractProduct type object.

3.2 Builder / Factory Method

MFC uses a variant of the builder pattern with its document/view architecture. A document template object (class CDocTemplate or its MFC-provided subclass) is instantiated with three CRuntimeClass object arguments that can be thought of as a builder object. The CRuntimeClass objects contain a CreateObject() method that can be used to create objects of classes CDocument, CFrameWnd, and CView or their subclasses.

The following figures compare the MFC model with the builder pattern and the factory method pattern. Only one builder method is shown for clarity.

Figure 3.3: The standard builder structure.

Figure 3.4: The CDocTemplate structure.

Figure 3.5: The standard factory method structure.

The MFC model does not accurately follow the builder pattern. Since CRuntimeClass objects and dynamic creation are used again, there is no need to subclass CDocTemplate in the application, and CreateDocument() can still produce objects of different classes. An application-specific subclass of CDocument can used, similarly as with the factory method pattern, by specifying the corresponding CRuntimeClass object.

3.3 Other Creational Patterns

The other creational patterns prototype and singleton were not found in MFC. The prototype pattern seems unnecessary, since the abstract factory pattern is in use. The singleton pattern could well have been used in implementing CWinApp objects, which represent the application and should not be instantiated more than once. MFC relies on the programmer to define only one static instance of a subclass of CWinApp.

4 Structural Patterns in MFC

Structural patterns are often found in user interface toolkits, and thus we decided to analyze the user interface related classes in MFC to find structural patterns. We expected to find at least adapter, composite, decorator, facade and proxy patterns.

4.1 Adapter Patterns

Because MFC is a multiplatform toolkit, the abstraction of the drawing routines could easily be done by using adapter patterns. We looked into this in the drawing code of MFC, but it looks like the abstraction has been done by supplying (stripped down) versions of the underlying Win32-interface libraries for other platforms than Windows. Looking at the afxwin.h headerfile was quite depressing, because it looks like Microsoft has left out a lot of functionality from the Macintosh version, functionality that could have easily been implemented using adapter patterns.

4.2 Composite Patterns

Looking at the MFC class diagram we noticed that all of the classes for windows support, like frame windows, dialog boxes and views were derived from the CWnd class, and tried to find composite patterns in this. Indeed, a lot of the functionality of the CWnd class can be identified as a composite pattern, because the controls are derived from the CWnd class. The controls are used as children of the other derived classes, and thus we have a tree-like structure where there is a uniform interface for accessing individual controls and compositions of controls (like dialogs).

Figure 4.1: Composite Pattern in Window Support

Examples of functionality that is treated uniformly includes moving and resizing, getting and setting various attributes and creational functions. The CWnd class contains a lot of specific functionality that cannot be used in all subclasses (controls cannot have menus, for example), so this is not a pure composite pattern in that respect.

4.3 Decorator Patterns

Decorator patterns are normally found in user interface components, typically for adding things like additional controls and decorations like scrollbars and borders. By looking at the hierarchy chart of the MFC class library we noticed that Microsoft has used the traditional method of subclassing, instead of using decorator patterns (see figure 4.2)

Figure 4.2: Scrolled Views through Subclassing

It would have been quite easy to use a decorator pattern in this case, but subclassing seems to be the way things are done in MFC everywhere.

4.4 Facade Patterns

The output of graphics in MFC (and more generally, Win32) is handled through a device context, which can be identified as a facade pattern. One can use the same functions for drawing graphics on the screen, printing, previewing and saving it to a metafile (see figure 4.3).

Figure 4.3: Graphics with Facade Pattern

The device context abstracts all of the details related to the different medias, like how to print color graphics on a black and white printer. The handling of the printers is further abstracted through the use of device drivers for different printers, but this is not related to facade patterns.

4.5 Proxy Patterns

Proxies are used to control the access to objects, and this is something that is needed for OLE. It was no suprise to find a proxy pattern in the OLE code, though it was a bit hidden behind a wrapper (COleObjectFactory). The proxy is used to check the license of the calling code, and it forwards the call if the license is valid.

Figure 4.4: License Checking in OLE

The license checking can be overriden to provide an user supplied version. The license check is done before creating the actual object, so only one check is needed at the beginning. After the first check the creation is done, and no further checking is required, because the code can use the object created by the factory directly.

5 Behavioral patterns in MFC

At the heart of the MFC framework are the concepts of document and view. A document is a data object with which the user interacts in an editing session. Together these:

  • Contain, manage, and display your application-specific data
  • Provide an interface consisting of document data variables for manipulating the data
  • Participate in writing and reading files
  • Participate in printing
  • Handle most of your application’s commands and messages

From this part of the framework we can find the following patterns:

5.1 Chain of Responsibility

In MFC Command Routing is a chain of responsibility. The user interface commands in MFC are implemented as command messages. The CCmdTarget class serves as the base class for all classes of objects that can receive and respond to messages. It implements the message-map interface that is used for mapping message IDs with corresponding command handlers. A command message is routed among participating classes until one of them provides a handler for the message (for derived classes the framework also searches the message map of their immediate base class).

[The Run member function of class CWinApp retrieves messages and dispatches them to the appropriate window. Most command messages are sent to the main frame window of the application. The WindowProc predefined by the class library gets the messages and routes them differently, depending on the category of message received.]

Standard Command Route in MFC:

Object that receives a command message

Object that tries to handle the message (in order)

MDI frame window (CMDIFrameWindow)

  1. Active CMDIChildWnd
  2. This frame window
  3. Application (CWinApp object)

Document frame window (CFrameWindow, CMDIChildWnd)

  1. Active view
  2. This frame window
  3. Application (CWinApp object)

View

  1. This View
  2. Document attached to the view

Document

  1. This document
  2. Document template attached to the document

Dialog box

  1. This dialog box
  2. Window that owns the dialog box
  3. Application (CWinApp object)

For example when a menu item in an MDI application's menu is selected the message is routed along the following chain:

Figure 5.1 Standard command route for document/view model

  1. The main frame window
  2. The main frame window routes it to the currently active MDI child window
  3. The MDI child frame window gives its view a chance to handle the message before it checks its own message map
  4. The view checks first its own message map and if no handler is present routes it to the associated document
  5. The document checks it message map and if no handler is present routes the message to its document template
  6. The document template and from there it would return to the view and after that to the frame window and finally to the Application

Message routing ends as soon as one of the objects in this chain provides a handler for the message.

5.2 Iterator

MFC implements the following collection classes:

  • CArray - Template class for making arrays of arbitrary types
  • CList - Template class for making lists of arbitrary types
  • CMap - Template class for making maps with arbitrary key and value types
  • CTypedPtrArray - Template class for type-safe arrays of pointers.
  • CTypedPtrList - Template class for type-safe lists of pointers.
  • CTypedPtrMap - Template class for type-safe maps with pointers.

It doesn't however provide a common interface for traversing these collections.

Examples

To iterate an array use sequential index numbers with the GetAt member function:

CTypedPtrArray myArray;
for( int i = 0; i <>
{
    CPerson* thePerson = myArray.GetAt( i );
    ...
}

To iterate a list use the member functions GetHeadPosition and GetNext to work your way through the list:

CTypedPtrList myList;
POSITION pos = myList.GetHeadPosition();
while( pos != NULL )
{
    CPerson* thePerson = myList.GetNext( pos );
    ...
}

Use GetStartPosition to get to the beginning of the map and GetNextAssoc to repeatedly get the next key and value from the map, as shown by the following example:

CMap myMap;
POSITION pos = myMap.GetStartPosition();
 
while( pos != NULL )
{
    CPerson* pPerson;
    CString string;
    // Get key ( string ) and value ( pPerson )
    myMap.GetNextAssoc( pos, string, pPerson );
    // Use string and pPerson
}

A possible solution for this is to derive user classes for each of these and implement iterators with a common interface for them.

5.3 Mediator

Dialogs act as mediators for their controls, eg. buttons and lists. The user dialog class (derived from CDialog) contains the dialog controls as members. The controls generate command messages that are routed to the dialog and mapped by its message map to the appropriate message handlers that act on the dialog controls and/or data.

MFC uses its command routing system to notify the mediator thus making it unnecessary for the controls to address their mediator directly.

Figure 5.3 Dialog as mediator

5.4 Observer

In MFC the CDocument based classes act as observers for the views attached to them. Whenever user modifies document data via one of the views the view notifies the associated document by calling its UpdateAllViews() member. The document then notifies all views that are attached to it by calling their OnUpdate() member. The views can then redraw their data based on the hints passed to them as parameters.

Figure 5.4 Observer in document/view model

5.5 Template Method

MFC uses template method to implement the common functionalities of the framework. Users can create new classes by subclassing them from the existing MFC classes thus linking them with the framework. They can modify the functionality by overriding suitable member functions. The AppWizard tool in Visual C++ creates automatically a working skeleton for a user application's user interface thus making it quite easy to create a simple application using documents and views. Without AppWizard this can be a tedious task.

Figure 5.5 Main base classes for document/view model

CObject

Implements serialization - writing and reading object's data into a file. The member function Serialize() takes a CArchive class object as a parameter and objects can use it for their file operations (CArchive implements reading and writing of primitive types via operators << and >>). This method is not suitable for applications that use databases and therefore they have to use other methods for retrieving and storing their data.

CCmdTarget

This class implements the message map and message routing via OnCmdMsg() member. Its subclasses redefine this member and build up the frameworks standard message route. User classes can use overriding for modifying this behavior if they have special needs for handling messages in the application.

CWinApp

CWinApp is the base class for application classes. It provides eg. the message loop which acquires and dispatches Windows messages until the application receives a WM_QUIT message.

CDocument

Implements routines for document creation, deletion, loading and saving. Applications can have several different document classes acting as data stores for different kinds of data.

CWnd

Provides the base functionality of all window classes in the MFC. Several other MFC classes are derived from CWnd to provide specific window types - some of these are designed for further derivation, eg. CFrameWnd, CMDIFrameWnd, CMDIChildWnd, CView and CDialog. Users can add their own window classes by subclassing them directly from CWnd or one of the provided subclasses.

CView

CView (together with CDocument) implements the above mentioned Mediator pattern for controlling changes in document data and updating the associated views. It consists mainly of overrideable members that construct the standard drawing and printing algorithms of the framework.

6 Conclusions

It is our suspicion that design patterns have not been used intentionally in MFC, although some generally useful techniques can be identified as patterns. The toolkit relies heavily on subclassing, and almost all classes are derived from one base class. This is something that does not work very well with design patterns, as we noticed when we tried to identify the patterns. The use of macros all over the code did not simplify the task, and the sheer size of MFC was somewhat distracting at first.

By looking at the class hierarchy we identified the most promising areas for design patterns, and found them in many cases. The purity of the patterns in MFC can be questioned, which is not that surprising, considering the age and complexity of the toolkit. The library has grown far beyond the original specification both in depth and breadth. This results in a number of inconsistencies and inconvenient solutions. In general, extremely large and complex interfaces are substituted for cooperation between a larger number of classes.

The structure of applications that are implemented using MFC will not be ideal. Although, for example, documents and views are separate entities, their interconnections are complex and rigid, and there has been no attempt to separate the implementation of user interface command from the user interface itself, although the documentation suggests so. This does not, of course, prevent from implementing this separation using design patterns within the application code.

6.1 Use of Creational Patterns

The use of the standard dynamic creation mechanism within MFC makes it largely unnecessary to implement the various creational design patterns. This mechanism, that is implemented with a number of macros and a support class, brings a very useful addition to C++ as a programming language. Dynamic creation is similar to the dictionary-based abstract factory variant. The mechnism must be supported by most user derived classes, and its use is wide-spread within MFC.

In other respects, fewer creational patterns were found than what was expected, and the examples that were found were extremely difficult to classify as one pattern or the other. A more imaginative use could certainly have been possible and would probably have resulted in a more straight-forward structure in some cases.

6.2 Use of Structural Patterns

The user interface part of MFC would benefit from patterns, because of the fact that it is a multi-platform toolkit. The internal part is however more or less hardcoded for the existing platforms, which technically speaking is quite limiting, but actually it does not matter for the average application writer. The lack of direct support for the usage of structural patterns in the actual interface is discouraging, so one has to build something on top of MFC to actually benefit from patterns. This is something that one should consider anyway, given the fact that MFC exists for a limited number of platforms anyway.

6.3 Use of Behavioral Patterns

The template method is used throughout MFC to implement the functionality of its classes. Interactions between different types of classes are solved by using chain of responsibility, observer and mediator patterns. Users can introduce new classes to the existing framework by subclassing and most of the functionality can be modified by overriding appropriate base class member functions. The implementation of MFC uses these same methods for creating its own classes.

1 comment:

Fei said...

Where are the figures? I cannot see any.

ITUCU