Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
Frequency of use: high
using System;
namespace DoFactory.GangOfFour.Adapter
{
class MainApp
{
///
/// Entry point into console application.
///
static void Main()
{
// Non-adapted chemical compound
Compound unknown = new Compound(Chemical.Unknown);
unknown.Display();
// Adapted chemical compounds
Compound water = new RichCompound(Chemical.Water);
water.Display();
Compound benzene = new RichCompound(Chemical.Benzene);
benzene.Display();
Compound alcohol = new RichCompound(Chemical.Alcohol);
alcohol.Display();
// Wait for user
Console.Read();
}
}
// "Target"
class Compound
{
private Chemical name;
private float boilingPoint;
private float meltingPoint;
private double molecularWeight;
private string molecularFormula;
// Constructor
public Compound(Chemical name)
{
this.name = name;
}
public virtual void Display()
{
Console.WriteLine("\nCompound: {0} -- ", Name);
}
// Properties
public Chemical Name
{
get{ return name; }
}
public float BoilingPoint
{
get{ return boilingPoint; }
set{ boilingPoint = value; }
}
public float MeltingPoint
{
get{ return meltingPoint; }
set{ meltingPoint = value; }
}
public double MolecularWeight
{
get{ return molecularWeight; }
set{ molecularWeight = value; }
}
public string MolecularFormula
{
get{ return molecularFormula; }
set{ molecularFormula = value; }
}
}
// "Adapter"
class RichCompound : Compound
{
private ChemicalDatabank bank;
// Constructor
public RichCompound(Chemical name) : base(name)
{
}
public override void Display()
{
// Adaptee
bank = new ChemicalDatabank();
// Adaptee request methods
BoilingPoint = bank.GetCriticalPoint(Name, State.Boiling);
MeltingPoint = bank.GetCriticalPoint(Name, State.Melting);
MolecularWeight = bank.GetMolecularWeight(Name);
MolecularFormula = bank.GetMolecularStructure(Name);
base.Display();
Console.WriteLine(" Formula: {0}", MolecularFormula);
Console.WriteLine(" Weight : {0}", MolecularWeight);
Console.WriteLine(" Melting Pt: {0}", MeltingPoint);
Console.WriteLine(" Boiling Pt: {0}", BoilingPoint);
}
}
// "Adaptee"
class ChemicalDatabank
{
// The Databank 'legacy API'
public float GetCriticalPoint(Chemical compound, State point)
{
float temperature = 0.0F;
// Melting Point
if (point == State.Melting)
{
switch (compound)
{
case Chemical.Water : temperature = 0.0F; break;
case Chemical.Benzene : temperature = 5.5F; break;
case Chemical.Alcohol : temperature = -114.1F; break;
}
}
// Boiling Point
else if (point == State.Boiling)
{
switch (compound)
{
case Chemical.Water : temperature = 100.0F; break;
case Chemical.Benzene : temperature = 80.1F; break;
case Chemical.Alcohol : temperature = 78.3F; break;
}
}
return temperature;
}
public string GetMolecularStructure(Chemical compound)
{
string structure = "";
switch (compound)
{
case Chemical.Water : structure = "H20"; break;
case Chemical.Benzene : structure = "C6H6"; break;
case Chemical.Alcohol : structure = "C2H6O2"; break;
}
return structure;
}
public double GetMolecularWeight(Chemical compound)
{
double weight = 0.0;
switch (compound)
{
case Chemical.Water : weight = 18.015; break;
case Chemical.Benzene : weight = 78.1134; break;
case Chemical.Alcohol : weight = 46.0688; break;
}
return weight;
}
}
// Enumerations
public enum Chemical
{
Unknown,
Water,
Benzene,
Alcohol
}
public enum State
{
Boiling,
Melting
}
}
.NET optimized sample code
The .NET optimized code demonstrates the same code as above but uses more modern, built-in .NET features. In this example, abstract classes have been replaced by interfaces because the abstract classes do not contain implementation code. Continents are represented as enumerations. The AnimalWorld constructor dynamically creates the desired abstract factory using the Continent enumerated values.
Code in project: DoFactory.GangOfFour.Abstract.NetOptimized
Abstract Factory: when and where use it
The Abstract Factory pattern provides a client with a class that creates objects that are related by a common theme. The classic example is that of a GUI component factory which creates UI controls for different windowing systems, such as, Windows, Motif, or MacOS. If you’re familiar with Java Swing you’ll recognize it as a good example of the use of the Abstract Factory pattern to build UI interfaces that are independent of their hosting platform. From a design pattern perspective, Java Swing succeeded, but applications built on this platform perform poorly and are not very interactive or responsive compared to native Windows or native Motif applications.
Over time the meaning of the Abtract Factory pattern has changed somewhat compared to the original GoF definition. Today, when developers talk about the Abstract Factory pattern they do not only mean the creation of a ‘family of related or dependent’ objects but also include the creation of individual object instances.
Next are some reasons and benefits for creating objects using an Abstract Factory rather than calling constructors directly:
Constructors are limited in their control over the overall creation process. If your application needs more control consider using a Factory. These include scenarios that involve object caching, sharing or re-using of objects, and applications that maintain object and type counts.
There are times when the client does not know exactly what type to construct. It is easier to code against a base type or interface and a factory can take parameters or other context-based information to make this decision for the client. An example of this are the provider specific ADO.NET objects (DbConnection, DbCommand, DbDataAdapter, etc).
Constructors don’t communicate their intention very well because they must be named after their class (or Sub New in VB.NET). Having numerous overloaded constructors may make it hard for the client developer to decide which constructor to use. Replacing constructors with intention-revealing creation methods are sometimes preferred. An example follows:
Several overloaded constructors. Which one should you use?
// C#
public Vehicle (int passengers)
public Vehicle (int passengers, int horsePower)
public Vehicle (int wheels, bool trailer)
public Vehicle (string type)
' VB.NET
public Sub New (Byval passengers As Integer)
public Sub New (Byval passengers As Integer, _
Byval horsePower As Integer)
public Sub New (Byval wheels As Integer wheels, _
Byval trailer As Boolean)
public Sub New (Byval type As String)
The Factory pattern makes code more expressive and developers more productive
// C#
public Vehicle CreateCar (int passengers)
public Vehicle CreateSuv (int passengers, int horsePower)
public Vehicle CreateTruck (int wheels, bool trailer)
public Vehicle CreateBoat ()
public Vehicle CreateBike ()
' VB.NET
public Function CreateCar (Byval passengers As Integer) As Vehicle
public Function CreateSuv (Byval passengers As Integer, _
Byval horsePower As Integer) As Vehicle
public Function CreateTruck (Byval wheels As Integer, _
Byval trailer As Boolean) As Vehicle
public Function CreateBoat () As Vehicle
public Function CreateBike () As Vehicle
Abstract Factory in the .NET Framework
ADO.NET 2.0 includes two new Abstract Factory classes that offer provider independent data access techniques. They are: DbProviderFactory and DbProviderFactories. The DbProviderFactory class creates the ‘true’ (i.e. the database specific) classes you need, such as SqlClientConnection, SqlClientCommand, and SqlClientDataAdapter. Each managed provider (such as SqlClient, OleDb, ODBC, and Oracle) has its own DbProviderFactory class. DbProviderFactory objects are created by the DbProviderFactories class, which itself is a factory class. In fact, it is a factory of factories -- it manufactures different factories, one for each provider.
When Microsoft talks about Abstract Factories they mean types that expose factory methods as virtual or abstract instance functions and return an abstract class or interface. Below is an example from .NET:
// C#
public abstract class StreamFactory
{
public abstract Stream CreateStream();
}
' VB.NET
Public MustInherit Class StreamFactory
Public MustOverride Function CreateStream() As Stream
End Class
In this scenario your factory type inherits from StreamFactory and is used to dynamically select the actual Stream type being created:
// C#
public class MemoryStreamFactory : StreamFactory
{
...
}
' VB.NET
Public Class MemoryStreamFactory
Inherits StreamFactory
...
End Class
The naming convention in .NET is to appends the word ‘Factory’ to the name of
that is being created. For example, a class that manufactures widget objects would
named WidgetFactory. A search through the libraries for the word ‘Factory’ reveals numerous classes that are implementations of the Factory design pattern.
No comments:
Post a Comment