Introduction
In the section of Design Patterns [ GoF ] titled "Creational Patterns", four patterns based directly on the factory concept are documented. These are Abstract Factory , Factory Method , Builder and Prototype . While the book documents a number of issues relating to these patterns, the list given is far from exhaustive. One significant omission is that (as far as I can see) only the issues to do with creating objects are considered, without regard to how they are disposed of. This is important, given that one purpose of a factory is to encapsulate the creation mechanism.
I've been giving this problem a lot of thought recently, and in this article I would like to present the ideas I've come up with. Please read critically! I am very interested in hearing about other peoples' ideas on this topic too.
In this article I refer simply to factory patterns , or even more simply, factories . I don't believe it is useful to talk about on specific factory patterns, because individual patterns do not occur in isolation in software design. These is too much overlap to make it worth while treating any factory pattern individually. This is inevitable because design patterns are not themselves designs. Rather, they represent approaches to solving certain problems. Further, some design patterns use other design patterns, often with the former specialising the latter. For example (as John Vlissides points out in Pattern Hatching [ Vlissides ]) the Abstract Factory pattern uses the Factory Method pattern.
Example Class Hierarchy
Before continuing, we need an example to work with. Consider a GUI multi-document system, which supports a Word Processor, Database, Spreadsheet etc., where the document classes have a common ABC called Doc . This is a very common example but I feel it works well in these situations. The following UML diagram shows the class hierarchy (only the word processor and database are included, for simplicity):
There will be a corresponding view hierarchy:
In addition there will be a requirement for some method of viewing and updating the properties of each document. This is normally done using a popup dialog box in a GUI system. Here the properties will be abstracted as a class called PropertiesUI . This leads to the following hierarchy:
Disposal
Before proceeding with the task of suggesting a solution to the disposal problem, I would like to make a couple of observations regarding the C++ programming language in this context.
-
In C++ the burden of deletion of objects rests with the programmer (in some other languages such as Java for example, it is not a problem because garbage collection takes care of things). This means our design must provide a means of disposing of the objects created by a factory.
-
C++ provides several ways to allocate memory for objects (automatic, static, plus more than one way to allocate dynamically on the heap). Since the exact creation mechanism is encapsulated within the factory, clients can not make any assumptions about the mechanism of disposal.
The approach used in the Design Patterns C++ examples is to assume that objects were created using new , so clients must use delete . In reality, it is dangerous to place this burden on the client, because it exposes the creation mechanism, the encapsulation of which is a purpose of the factory. Also, consider the following two techniques in which the usual form of new is not used (at least, not directly):
-
The factory may use pre-allocated memory, constructing its objects using the placement form of new .
-
Sometimes the factory may avoid the overhead of creating new objects, by recycling objects previously disposed of.
I would like to continue by discussing two possible solutions to the disposal problem:
-
Disposal by the factory : in this solution the object is passed back to the factory for disposal.
-
Use a handle : in this solution, rather than return a pointer, the factory returns a handle (or proxy) to the object.
These solutions all have points for and against them. As usual there is no single best option; it all comes down to the individual design criteria.
Disposal by the Factory
In my experience, this is the most frequently quoted solution is to simply pass the object back to the factory. After all, the factory knows how to dispose of what it created, having provided the creation mechanism. However, things are not quite that simple, as in OO systems it is unlikely that the concrete class will be known for very long following object creation. Knowing which type of factory to pass it to for disposal may not be possible without first going through a type-laundering process. If the need for type laundering is to be avoided, then the deletion mechanism must be the same for each type of concrete class. In other words, this solution forces each type of factory to use the same creation mechanism. This may or may not be acceptable; it's a decision required at the design stage.
There is a further problem with this solution: the factory must be available at disposal time. This is not usually a problem with factories following the Abstract Factory design pattern, as the factory is an object in its own right. However, if the factory was of a type following the Factory Method or Prototype design patterns, the 'baggage' the object providing the factory function brings with it may make this difficult.
I do not intend to take this any further in this article as it depends heavily on the type of factory in use.
Use a Handle
Another solution is not to return the object directly but to return a handle to it. If the factory uses the simple form of new to create its product, the handle it returns can be std::auto_ptr . This has the downside is that it is difficult to manage more than one reference to the object simultaneously.
If the factory employs any of the creation techniques described above, then a proprietary handle is needed. Using a proprietary handle has the advantage that it allows a close coupling with the factory. In other words, the handle can have an intimate knowledge of the creation mechanism used by the factory. I propose to generalise the handle in the form of a 'smart pointer' and to start with the following sketch of a design for it.
template <typename ProductType> class ProductPtr { ProductType* product; public: ProductPtr(ProductType* inputProduct) : product(inputProduct) {} ~ProductPtr() { delete product; } ProductType* operator->() const { return product; } };
Remember, here we are concerned purely with the problem of the mechanism of disposing of the product. This is my excuse for leaving things like assignment and copy construction out of the above class, and ignoring issues like managing ownership ☺. Whether or not more than one handle to the same object is allowed or not is, in my view at least, a separate design decision. Ownership determines whether or not the handle should disose of the object, not how it should do so.
So far, we have a similar situation to simply using std::auto_ptr . The problem now, is to give ProductPtr knowledge of the disposal mechanism. We can not actually code this mechanism in ProductPtr , because we would end up with the same problem as we have when passing objects back to the factory for disposal. The Strategy design pattern can be used to solve this problem. Going back to the Doc , View and PropertiesUI classes above, let us introduce the following ABC.
class DisposalMechanism { public: virtual void operator() ( void* disposee) const = 0; };
Now we derive the following:
class BasicDisposalMechanism : public DisposalMechanism { BasicDisposalMechanism() {} BasicDisposalMechanism( BasicDisposalMechanism const&); BasicDisposalMechanism & operator=(BasicDisposalMechanism const&); public: virtual void operator()(void* disposee) const { delete disposee; } static BasicDisposalMechanism const & instance() { BasicDisposalMechanism inst; return inst; } };
Then we extend ProductPtr as follows:
template <typename ProductType> class ProductPtr { ProductType* product; DisposalMechanism& disposer; public: ProductPtr( ProductType* inputProduct, DisposalMechanism const& inputDisposer) : product(inputProduct), disposer(inputDisposer) {} ~ProductPtr() { disposer(product); } ProductType* operator->() const { return product; } };
References
[GoF] Design Patterns: Elements of Reusable Object-Oriented Software , Gamma, Helm, Johnson & Vlissides, Addison-Wesley.
[Coplien] Advanced C++ Programming Styles and Idioms , James O. Coplien, Addison-Wesley.
[Vlissides] Pattern Hatching: Design Patterns Applied , John Vlissides, Addison-Wesley.