Abstract
Design Patterns, Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides [also known as the Gang of Four (GOF)] has been a de facto reference for any Object-Oriented software developer. This article is Part I of a series of articles illustrating the GOF design patterns in C#. We will discuss the Abstract Factory, Factory Method, and Builder patterns. The Singleton pattern is already widely known and is not discussed here. It is assumed the reader is familiar with basic C# syntax and conventions, and not necessarily details of the .NET Framework.
Background
In Design Patterns, each pattern is described with its name (and other well-known names); the motivation behind the pattern; its applicability; the structure of the pattern; class/object participants; participant collaborations; pattern consequences; implementation; sample code; known uses; and related patterns. This article will only give a brief overview of the pattern, and an illustration of the pattern in C#.
A design pattern is not code, per se, but a "plan of attack" for solving a common software development problem. The GOF had distilled the design patterns in their book into three main subject areas: Creational, Structural, and Behavioral. This article deals with the Creational design patterns, or how objects get created.
This article is meant to illustrate the design patterns as a supplement to their material. It is recommended that you are familiar with the various terms and object diagram methods used to describe the design patterns as used by Gamma et al. If you're not familiar with the diagrams, they should be somewhat self-explanatory once viewed. The most important terms to get your head around are abstract and concrete. The former is a description and not an implementation, while the latter is the actual implementation. In C#, this means an abstract class is an interface
, and the concrete class implements that interface.
Creational Patterns
To quote Gamma et al., "Creational design patterns abstract the instantiation process. They help make a system independent of how its objects are created, composed, and represented. A class creational pattern uses inheritance to vary the class that's instantiated, whereas an object creational pattern will delegate instantiation to another object." We separate the object from how it's directly created. As we delve into the patterns the reasons for doing so should become clear.
Abstract Factory
The intent of the pattern, as described in Design Patterns, is to "Provide an interface for creating families of related or dependent objects without specifying their concrete classes." The pattern structure is shown here:
This pattern is quite useful in systems where we want to expose a library of products to be used by a client, independent of their implementations. It is worthwhile to note that the Factories used by the client are typically Singletons, and the Abstract Factory is only an interface, the concrete implementation of the factory actually creates the products. For this, the interface defines a Factory Method (another creational design pattern) for each product, which is in turn implemented in the concrete factory.
In our C# illustration, we wish to create similar airplanes from two different manufacturers. Please view the sample source code file abstract_factory.cs
for this example.
Both manufacturers sell single-engine and multi-engine land-based airplanes, of similar design, but with differing flight characteristics. We define an interface for the Abstract Factory as well as the airplanes in their own namespace as follows (we could also create an IAirplane interface to base the Airplanes on, but we're not going to do that for this illustration):
public interface ISingleEngineAirplane
{
void Fly();
}
public interface IMultiEngineAirplane
{
void Fly();
}
public interface IAirplaneFactory
{
ISingleEngineAirplane CreateSingleEngine();
IMultiEngineAirplane CreateMultiEngine();
}
We implement ISingleEngineAirplane
and IMultiEngineAirplane
for the two manufacturers, Cessna
and Piper
as private classes, as well as the public concrete factories for them in their own namespace. When a client needs an aircraft, they just import the interface library, the concrete library, and use the appropriate concrete factory:
public class TheClient
{
static CessnaFactory m_oCF = new CessnaFactory();
static PiperFactory m_oPF = new PiperFactory();
public static void Main(string[] args)
{
ISingleEngineAirplane oSE;
IMultiEngineAirplane oME;
Console.WriteLine("Creating Cessna SE, Piper ME...");
oSE = m_oCF.CreateSingleEngine();
oME = m_oPF.CreateMultiEngine();
oSE.Fly();
oME.Fly();
Console.WriteLine("Creating Piper SE, Cessna ME...");
oSE = m_oPF.CreateSingleEngine();
oME = m_oCF.CreateMultiEngine();
oSE.Fly();
oME.Fly();
}
}
It is worthwhile to note that it is possible to further use an AbstractFactory pattern to create the concrete factories as well!
Factory Method
One drawback to using an Abstract Factory is that to in order to support new products from your factories you have to extend the factories and all the subclasses. One way around this is to use the Factory Method pattern. Please view the sample source code file factory_method.cs
for this example.
The Factory Method pattern is useful when (GOF):
- a class can't anticipate the class of objects it must create.
- a class wants its subclasses to specify the objects it creates.
- classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate.
It's structure is shown here:
In our example, our concrete factories define numeric constants to what kind of objects it can create (you can use strings or other identifying types):
public class CessnaFactory : IAirplaneFactory
{
public const int SEL = 1;
public const int MEL = 2;
public virtual IAirplane Create(int t)
{
switch(t)
{
case SEL:
return new CessnaSingleEngine();
case MEL:
return new CessnaMultiEngine();
default:
throw new Exception("Unknown Aircraft Type!");
}
}
}
The client, would use the factory thus:
IAirplane o = m_oCF.Create(CessnaFactory.SEL);
If we wished to extend the factory to build other objects we could derive from the Factory, and override the Create
method, of course calling the base class' Create
method:
public class BetterCessnaFactory : CessnaFactory
{
public const int CITATION = MEL + 1;
override public IAirplane Create(int t)
{
try
{
return base.Create(t);
}
catch
{
switch(t)
{
case CITATION:
return new CessnaCitation();
default:
throw new Exception("Unknown Aircraft Type!");
}
}
}
}
The Factory Method pattern is widely used in COM. It's quite informative and educational to peruse the C++ ATL source code, and see it for yourself.
Builder
A Builder is a creational design pattern meant to "Separate the construction of a complex object from its representation so that the same construction process can create different representations." (GOF) Continuing with our airplane manufacturer analogy, building an airplane consists of assembling the sub-parts. Our airplane creation algorithm should be independent of the parts that make it up and how they are assembled. Please view the sample source code file builder.cs for this example.
The builder design pattern structure looks like this:
We begin with the parts, defining an abstract part builder, abstract airplane part, and abstract director (assembler) as interfaces which we will make concrete by implementing them later for our specific parts:
public interface IAirplanePart
{
string Manufacturer { get; }
}
public interface IPartBuilder
{
bool BuildPart()
}
public interface IAirplane
{
IAirplanePart Airframe { get; }
IAirplanePart Engine { get; }
}
public interface IAirplaneAssembler
{
bool Construct();
}
We then create the concrete implementations for our various parts, for example, a Continental engine:
public class ContinentalEngine : IAirplanePart, IPartBuilder
{
private const string MANUFACTURER = "Continental";
public string Manufacturer { get { return MANUFACTURER; } }
public bool BuildPart()
{
Console.WriteLine("Assembling a {0} engine...",
MANUFACTURER);
return true;
}
}
We've combined the GetResult()
in the pattern structure into the actual BuildPart()
for brevity. We further make a concrete director (assembler) for our Cessna 172:
public class C172 : IAirplane, IAirplaneAssembler
{
private C172Airframe m_oAirframe;
private LycomingEngine m_oEngine;
public IAirplanePart Airframe { get { return m_oAirframe; } }
public IAirplanePart Engine { get { return m_oEngine; } }
public bool Construct()
{
m_oAirframe = new C172Airframe();
m_oEngine = new LycomingEngine();
return m_oAirframe.BuildPart() && m_oEngine.BuildPart();
}
}
In order to use these fun, new classes in a client, we import the interface library, the concrete library, and build a Cessna 172:
public static void Main(string[] args)
{
C172 o = new C172();
Console.WriteLine("Constructing a Cessna 172...");
if (o.Construct())
{
Console.WriteLine("Success!");
Console.WriteLine("Airframe: {0}",
o.Airframe.Manufacturer);
Console.WriteLine("Engine: {0}",
o.Engine.Manufacturer);
}
else
Console.WriteLine("Failed!");
}
If all goes well, we should see:
Constructing a Cessna 172...
Assembling a Cessna 172 airframe...
Assembling a Lycoming engine...
Success!
Airframe: Cessna 172
Engine: Lycoming
This example is highly simplified, but I hope it illustrates the pattern in a straightforward manner. In a real-world implementation, I probably would not combine the airplane part and the builder of that part, as building a part isn't usually the responsibility of the part itself. I would also not combine the "director" with the actual object being assembled for the same reason.
Conclusions
Creational design patterns allow for great flexibility in how your software solves the problem of creating objects:
Using an Abstract Factory allows you to control the kind of classes you create at runtime. It also encapsulates the functionality of object creation, isolating the implementation from clients, since clients only use the interfaces. As you can see from the example in this article, exchanging product families is simple.
Factory Methods. Design Patterns has this to say about the Factory Method pattern: "A potential disadvantage of factory methods is that clients might have to subclass the Creator class just to create a particular ConcreteProduct object. Subclassing is fine when the client has to subclass the Creator class anyway, but otherwise the client now must deal with another point of evolution." In the sample source code, you can see this statement in action.
The Builder creational pattern lets you vary a product's internal structure, as well as how it gets assembled. Because you construct the object through an abstract interface, you can define a new kind of builder for a product to assemble it from different structures. The client need not know anything about the construction process, nor the parts that make up a product. You get a high degree of control in the construction process.
Stay tuned for future articles...
Building the Samples
Unzip the source files to the folder of your choice. Start a shell (cmd.exe), navigate to the folder and type nmake
. You may have to modify the Makefile to point to the correct folder where your .NET Framework libraries live.
History
2002-11-06 Updated builder.cs constants to private constants :)
2002-11-04 Slight revision
2002-11-03 Initial Revision
References
- Design Patterns, Elements of Reusable Object-Oriented Software. Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Addison Wesley Longman, Inc. 1988. ISBN 0-201-63498-8.