.NET Abstract Factory

This article presents three scenarios where an Abstract Factory may prove useful. It also highlights areas where abstract factory has been used within the .NET framework and the Patterns and Practices Enterprise Library.

Design Patterns

Article 2

Author: Douglas Minnaar

Level: Novice – Intermediate

Prerequisites:

  • Understanding of Object Oriented Programming
  • The examples will be demonstrated in C#.NET therefore an understanding of C#.NET code is required

Download code here

Summary

It is not the intent of the Design Pattern Series to focus on providing a theoretical knowledge dump of all there is to know about design patterns. There are many books that do that already. Instead, this series will focus on providing lots of practical examples. However, there will be some theory to help address important points concerning design patterns. I use the theory of design patterns mostly as a guide and instead make references to good design pattern books for more detail explanation. Think of this series as a ‘Design Patterns by example’ series. The target audience is that of a novice to intermediate software developer.

Part 2 – GOF Design Patterns

In part 2 we look at another creational design pattern namely the Abstract Factory. In terms of the Factory Method and Abstract Factory, the whole idea is to isolate client code from changes. In other words, one would like to be able to make changes to the way an object is created without affecting the client code in terms of changes having to be made.

Abstract Factory

Please refer to the GOF book or any other good design pattern book for more detailed information pertaining to the Abstract Factory.

The intent of the Abstract Factory is to provide an interface for creating families of related or dependent objects without specifying their concrete classes.

One should consider using an Abstract Factory when

  • a system must be decoupled from how its products are created. In other words, a system must be able to create products without specifying concrete product classes. Therefore, one is able to isolate ones concrete classes from the client
  • a system must be configured with one of a set of products
  • a constraint must be enforced so that a group of products are designed to be used together
  • one wishes to expose a library of classes as a set of interfaces. Therefore, a client can only change a product through it's interface
  • one wishes to change the configuration of how products are created without changing client code

A caveat of the Abstract Factory is that when one wishes to add another product family, one is required to change the Abastract Factory interface. This has the affect that one is required to change the interface of the Abstract Factory subclasses. There are ways around this problem. One may choose to implement an Abstract Factory that exposes a single Factory Method that can be used to create various products based on certain input criteria. The Factory Method would then have the responsibility in terms of deciding what product to create. Therefore, the creation of products is not left to the Abstract Factory subclasses to determine what product to create, but is left to a single Factory Method instead.

Structure

The following diagrams illustrate the basic Abstract Factory structure.

In this diagram, the Abstract Factory determines what products to create. I then have another Factory (Client) that is used to create an Abstract Factory using a Factory Method (CreateProductFactory())

Click to enlarge

BaseProductFactory

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public abstract class BaseProductFactory

  {

    protected BaseProductFactory()

    {

    }

 

    public abstract BaseProductA ProductA { get; }

 

    public abstract BaseProductB ProductB { get; }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public class ProductFactory1 : BaseProductFactory

  {

    public ProductFactory1()

    {

    }

 

    public override BaseProductA ProductA

    {

      get { return new ProductA1(); }

    }

 

    public override BaseProductB ProductB

    {

      get { return new ProductB1(); }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public class ProductFactory2 : BaseProductFactory

  {

    public ProductFactory2()

      : base()

    {

    }

 

    public override BaseProductA ProductA

    {

      get { return new ProductA2(); }

    }

 

    public override BaseProductB ProductB

    {

      get { return new ProductB2(); }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public abstract class BaseProductA

  {

    protected BaseProductA()

    {

    }

 

    public abstract string Description { get; } 

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public class ProductA1 : BaseProductA

  {

    public ProductA1()

      : base()

    {

    }

 

    public override string Description

    {

      get { return "Product A1"; }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public class ProductA2 : BaseProductA

  {

    public ProductA2()

      : base()

    {

    }

 

    public override string Description

    {

      get { return "Product A2"; }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public abstract class BaseProductB

  {

    protected BaseProductB()

    {

    }

 

    public abstract string Name { get; }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public class ProductB1 : BaseProductB

  {

    public ProductB1()

      : base()

    {

    }

 

    public override string Name

    {

      get { return "ProductB1"; }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public class ProductB2 : BaseProductB

  {

    public ProductB2()

      : base()

    {

    }

 

    public override string Name

    {

      get { return "ProductB2"; }

    }

  }

}

Client

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Structure

{

  public static class Client

  {

    public static BaseProductFactory CreateProductFactory(string factory)

    {

      switch (factory.ToUpper().Trim())

      {

        case("PRODUCTFACTORY1"):

          return new ProductFactory1();

        case("PRODUCTFACTORY2"):

          return new ProductFactory2();

        default:

          return null;

      }

    }

  }

}

The test code for the Abstract Factory Structure is as follows:

public static void TestProductFactory1()

{

  Console.WriteLine("-- Testing Product Factory 1 --");

  Console.WriteLine();

 

  BaseProductFactory factory = Client.CreateProductFactory("PRODUCTFACTORY1");

 

  BaseProductA productA = factory.ProductA;

  Console.WriteLine("Product A Description for Product Factory 1 : {0}", productA.Description);

 

  BaseProductB productB = factory.ProductB;

  Console.WriteLine("Product B Name for Product Factory 1 : {0}", productB.Name);

}

 

public static void TestProductFactory2()

{

  Console.WriteLine("-- Testing Product Factory 2 --");

  Console.WriteLine();

 

  BaseProductFactory factory = Client.CreateProductFactory("PRODUCTFACTORY2");

 

  BaseProductA productA = factory.ProductA;

  Console.WriteLine("Product A Description for Product Factory 2 : {0}", productA.Description);

 

  BaseProductB productB = factory.ProductB;

  Console.WriteLine("Product B Name for Product Factory 2 : {0}", productB.Name);

}

The result of the Structure test is as follows:

StructureOutput


Examples

Please note that the names I use to represent certain entities in the examples are intended to be fictitious.

Example 1 : Bank

We will build on the knowledge acquired from the Banking example for the Factory Method from the previous article. Lets pretend that we are required to create a simple ATM (Automatic Teller Machine) that allows one to not only perform banking operations, but also other services that may be made available from Third Party vendors. Typically, one would withdraw money, query balances, do transfers, etc at an ATM. What if we could make it possible to extend our application in such a way that one could 'plug-in' additional services without having to redesign our application. A third party service may be something like being able to pay your speeding fines (something that I use quiet often at our local ATM machines), or being able to purchase air time for a pre-paid cellular phone account. However, we want a client to be able to access these services regardless of the clients banking provider. Therefore, whether a client has a Bank_ABC ATM card or a Bank_XYZ ATM card, the ATM application will provide a common interface into using a family of services without specifying the specifics of that service. Therefore, our interface must be an abstraction of the services that are available. By allowing for this mechanism, one can add additional Banking vendor services or third party services without having to redesign the ATM application. The reason for this is because we are essentially striving for a design whereby we decouple the interface from how the services (products) will be created. Because we do not have an ATM machine or an ATM card, we will only use a PIN (Personal Identification Number) to uniquely identify a client. This will in turn allow the BankProviderService to retrieve the appropriate services.

The following diagram represents an overview of our very simple ATM application.

Click to enlarge

We will provide the following services (products) for our BankProviderService (Factory).

  • BankAccountProvider - Provide client with the relevant BankAccountProvider service that will allow a client to view and manage bank account details.

AccountProvider

using System;

using System.Collections.Generic;

 

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public abstract class BaseAccountProvider

  {

    protected BaseAccountProvider(BaseAuthorizationToken auhtorizationToken)

    {

      AuthorizationToken = authorizationToken;

    }

 

    /// <summary>

    /// Retrieve Account details based on the provided account number

    /// as well as the authorization token associated with this instance

    /// </summary>

    /// <param name="accountNumber">Account Number</param>

    /// <returns>Account</returns>

    public abstract BaseAccount GetAccount(string accountNumber);

 

    /// <summary>

    /// Retrieve list of accounts based on the authorization token

    /// assosiated with this instance

    /// </summary>

    public abstract List<BaseAccount> AccountList { get; }

 

    private BaseAuthorizationToken authorizationToken;

    /// <summary>

    /// The Authorization token provides only the neccessary

    /// information to indicate the authorization details required

    /// to access account information

    /// </summary>

    public BaseAuthorizationToken AuthorizationToken

    {

      get { return authorizationToken; }

      protected set { authorizationToken = value; }

    }

 

  }

}

using System.Collections.Generic;

 

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public class BankOfMarsAccountProvider : BaseAccountProvider

  {

    public BankOfMarsAccountProvider(BaseAuthorizationToken authorizationToken)

      : base(authorizationToken)

    {

    }

 

    public override BaseAccount GetAccount(string accountNumber)

    {

      // Logic to retrieve a valid Account  based on provided

      // account number

      return new BankOfMarsAccount();

    }

 

    public override List<BaseAccount> AccountList

    {

      // Logic to retrieve a List of Accounts based on the

      // Authorization Token associated with this instance

      get { return new List<BaseAccount>(); }

    }

  }

}

using System.Collections.Generic;

 

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public class BankOfNeptuneAccountProvider : BaseAccountProvider

  {

    public BankOfNeptuneAccountProvider(BaseAuthorizationToken authorizationToken)

      : base(authorizationToken)

    {

    }

 

    public override BaseAccount GetAccount(string accountNumber)

    {

      // Logic to retrieve a valid Account  based on provided

      // account number

      return new BankOfNeptuneAccount();

    }

 

    public override List<BaseAccount> AccountList

    {

      // Logic to retrieve a List of Accounts based on the

      // Authorization Token associated with this instance

      get { return new List<BaseAccount>(); }

    }

  }

}

  • AuthorizationService - The AuthorizationService will merely authorize a client based on the provided PIN.

AuthorizationService

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  /// <summary>

  /// The BaseAuthorizationService is used to validate a user

  /// in terms of performing banking and third party service

  /// activities

  /// </summary>

  public abstract class BaseAuthorizationService

  {

    protected BaseAuthorizationService()

    {

    }

 

    /// <summary>

    /// Retrieve an Authorization Token based on a set of provided

    /// security credentials. I use a string for simplicity and

    /// demonstration purposes.

    /// </summary>

    /// <param name="requiredAuthorizationCredentials">Credentials</param>

    /// <returns>Authorization Token</returns>

    public abstract BaseAuthorizationToken GetAuthorizationToken(

      IBankProviderServiceCredentials bankProviderServiceCredentials);

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public class BankOfNeptuneAuthorizationService : BaseAuthorizationService

  {

    public BankOfNeptuneAuthorizationService()

      : base()

    {

    }

 

    public override BaseAuthorizationToken GetAuthorizationToken(

      IBankProviderServiceCredentials bankProviderServiceCredentials)

    {

      return new BankOfNeptuneAuthorizationToken();

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public class BankOfMarsAuthorizationService : BaseAuthorizationService

  {

    public BankOfMarsAuthorizationService()

      : base()

    {

    }

 

    public override BaseAuthorizationToken GetAuthorizationToken(

      IBankProviderServiceCredentials bankProviderServiceCredentials)

    {

      return new BankOfMarsAuthorizationToken();

    }

  }

}

  • ThirdPartyService - This is an abstract term to refer to any third party service that one would wish to add. The whole idea is that one can add many more services (whether they are third party or not). I only have one third party service (called ThirdPartyService) for demonstration purposes.

ThirdParty

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public abstract class BaseThirdPartyService

  {

    protected BaseThirdPartyService()

    {

    }

 

    /// <summary>

    /// Method1 is simply a placeholder to indicate a method

    /// that may form part of a third party service interface

    /// </summary>

    public abstract void Method1();

  }

}

using System;

 

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public class BankOfMarsThirdPartyService : BaseThirdPartyService

  {

    public BankOfMarsThirdPartyService()

      : base()

    {

    }

 

    public override void Method1()

    {

      Console.WriteLine("Bank of Mars Third Party Service Method 1");

    }

  }

}

using System;

 

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public class BankOfNeptuneThirdPartyService : BaseThirdPartyService

  {

    public BankOfNeptuneThirdPartyService()

      : base()

    {

    }

 

    public override void Method1()

    {

      Console.WriteLine("Bank of Neptune Third Party Service Method 1");

    }

  }

}

We use the following Factories (Represent Banking Provider Services that will provide access to a number of other ATM application services)

  • BaseBankProviderService
  • BankOfMarsProviderService
  • BankOfNeptuneProviderService

BankProviderService

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public abstract class BaseBankProviderService

  {

    protected BaseBankProviderService(

      IBankProviderServiceCredentials bankProviderServiceCredentials)

    {

      BankProviderServiceCredentials = bankProviderServiceCredentials;

    }

 

    public abstract BaseAccountProvider AccountProvider { get; }

 

    public abstract BaseAuthorizationService AuthorizationService { get; }

 

    public abstract BaseThirdPartyService ThirdPartyService { get; }

 

    private IBankProviderServiceCredentials bankProviderServiceCredentials;

 

    public IBankProviderServiceCredentials BankProviderServiceCredentials

    {

      get { return bankProviderServiceCredentials; }

      protected set { bankProviderServiceCredentials = value; }

    }

 

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public class BankOfMarsBankProviderService : BaseBankProviderService

  {

    public BankOfMarsBankProviderService(

      IBankProviderServiceCredentials bankProviderServiceCredentials)

      : base(bankProviderServiceCredentials)

    {

    }

 

    public override BaseAccountProvider AccountProvider

    {

      get

      {

        return new BankOfMarsAccountProvider(

          AuthorizationService.GetAuthorizationToken(

          BankProviderServiceCredentials));

      }

    }

 

    public override BaseAuthorizationService AuthorizationService

    {

      get { return new BankOfMarsAuthorizationService(); }

    }

 

    public override BaseThirdPartyService ThirdPartyService

    {

      get { return new BankOfMarsThirdPartyService(); }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public class BankOfNeptuneBankProviderService : BaseBankProviderService

  {

    public BankOfNeptuneBankProviderService(

      IBankProviderServiceCredentials bankProviderServiceCredentials)

      : base(bankProviderServiceCredentials)

    {

    }

 

    public override BaseAccountProvider AccountProvider

    {

      get

      {

        return new BankOfNeptuneAccountProvider(

          AuthorizationService.GetAuthorizationToken(

          BankProviderServiceCredentials));

      }

    }

 

    public override BaseAuthorizationService AuthorizationService

    {

      get { return new BankOfNeptuneAuthorizationService(); }

    }

 

    public override BaseThirdPartyService ThirdPartyService

    {

      get { return new BankOfNeptuneThirdPartyService(); }

    }

  }

}

Therefore, in order to create a BankProviderService, one is required to provide a PIN and the name of the Bank to the Atm static class. The Atm class exposes a Factory method that will be used to create the appropriate BankProviderService based on the provided bank name.

Atm

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Banking

{

  public static class Atm

  {

    public enum Bank

    {

      BankOfMars,

      BankOfNeptune

    }

 

    public static BaseBankProviderService Create(Bank bank, int PIN)

    {

      switch (bank)

      {

        case (Bank.BankOfMars):

          {

            // We could have used a factory method here to return

            // the appropriate credentials

            IBankProviderServiceCredentials credentials =

              new DefaultBankProviderServiceCredentials(PIN);

 

            return new BankOfMarsBankProviderService(credentials);

          }

        case (Bank.BankOfNeptune):

          {

            // We could have used a factory method here to return

            // the appropriate credentials

            IBankProviderServiceCredentials credentials =

              new DefaultBankProviderServiceCredentials(PIN);

 

            return new BankOfNeptuneBankProviderService(credentials);

          }

      }

 

      throw new Exception("An invalid Bank was specified.");

    }

  }

}

The code to test the BankProviderService is as follows:

public static void TestBankOfMarsProviderService()

{

  Console.WriteLine(

    "-- Testing Bank Of Mars Bank Provider Service --");

 

  Console.WriteLine();

 

  BaseBankProviderService service = Atm.Create(

    Atm.Bank.BankOfMars, 55555);

 

  Console.WriteLine("Bank Provider Service: {0}",

    service.GetType().Name);

 

  BaseAccountProvider accountProvider =

    service.AccountProvider;

 

  Console.WriteLine("Account Provider: {0}",

    accountProvider.GetType().Name);

 

  Console.WriteLine("Account: {0}",

    accountProvider.GetAccount("1234567890").GetType().Name);

 

  BaseAuthorizationService authorizationService =

    service.AuthorizationService;

 

  Console.WriteLine("Authorization Service: {0}",

    authorizationService.GetType().Name);

 

  BaseThirdPartyService thirdPartyService =

    service.ThirdPartyService;

 

  Console.WriteLine("Third Party Service: {0}",

    thirdPartyService.GetType().Name);

 

  Console.WriteLine();

 

  Console.WriteLine("-- End Test --");

}

 

public static void TestBankOfNeptuneBankProviderService()

{

  Console.WriteLine(

    "-- Testing Bank Of Neptune Bank Provider Service --");

 

  Console.WriteLine();

 

  BaseBankProviderService service = Atm.Create(

    Atm.Bank.BankOfNeptune, 55555);

 

  Console.WriteLine("Bank Provider Service: {0}",

    service.GetType().Name);

 

  BaseAccountProvider accountProvider = service.AccountProvider;

 

  Console.WriteLine("Account Provider: {0}",

    accountProvider.GetType().Name);

 

  Console.WriteLine("Account: {0}",

    accountProvider.GetAccount("1234567890").GetType().Name);

 

  BaseAuthorizationService authorizationService =

    service.AuthorizationService;

 

  Console.WriteLine("Authorization Service: {0}",

    authorizationService.GetType().Name);

 

  BaseThirdPartyService thirdPartyService =

    service.ThirdPartyService;

 

  Console.WriteLine("Third Party Service: {0}",

    thirdPartyService.GetType().Name);

 

  Console.WriteLine();

 

  Console.WriteLine("-- End Test --");

}

The result of the BankProviderService test is as follows:

BankOutput

Example 2 - Game

Many are familiar with RTS (Real Time Strategy) games like Starcraft, Warcraft, Command and Conquer, etc. One of the common things that one is required to do is build structures that will allow one to build units from the structures. So, in the spirit of RTS games, we are going to create a simple implementation of how one might use the combination of Factory Methods and Abstract Factories to create structures and units.

The following high-level uml diagram represents the Abstract Factory design that we will use for this example.

Click to enlarge

Our game has two types of species. The Gaea Federation and the Borg. Our abstract factory must allow us to create a Barracks, Headquarters and a WarRoom for the two species. The structures will determine what units may be created. Therefore, we will create a family of structures (products) along with a family of units (products for the structures). This example will demonstrate two possible implementations for the abstract factory.

For the first implementation we will use a Factory for each family of structures.

 

BaseStructureFactory

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game

{

  public abstract class BaseStructureFactory

  {

    protected BaseStructureFactory()

    {

    }

 

    public abstract BaseBarracks Barracks { get; }

 

    public abstract BaseHQ HQ { get; }

 

    public abstract BaseWarRoom WarRoom { get; }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game

{

  public class BorgStructureFactory : BaseStructureFactory

  {

    public BorgStructureFactory()

      : base()

    {

    }

 

    public override BaseBarracks Barracks

    {

      get { return new BorgBarracks(); }

    }

 

    public override BaseHQ HQ

    {

      get { return new BorgHQ(); }

    }

 

    public override BaseWarRoom WarRoom

    {

      get { return new BorgWarRoom(); }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game

{

  public class GaeaFederationStructureFactory : BaseStructureFactory

  {

    public GaeaFederationStructureFactory()

      : base()

    {

    }

 

    public override BaseBarracks Barracks

    {

      get { return new GaeaFederationBarracks(); }

    }

 

    public override BaseHQ HQ

    {

      get { return new GaeaFederationHQ(); }

    }

 

    public override BaseWarRoom WarRoom

    {

      get { return new GaeaFederationWarRoom(); }

    }

  }

}

The BaseStructureFactory will allow one to create the required game structures. The game structures are shown below.

 

BaseBarracks

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Structures

{

  public abstract class BaseBarracks

  {

    public enum Unit

    {

      FootmanSoldier,

      Insidious,

      StarshipTrooper

    }

 

    protected BaseBarracks()

    {

    }

 

    public abstract BaseBarracksUnit CreateUnit(Unit unit);

 

    public abstract string Name { get; }

 

    public abstract void Upgrade();

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Structures

{

  public class BorgBarracks : BaseBarracks

  {

    public BorgBarracks()

      : base()

    {

    }

 

    public override BaseBarracksUnit CreateUnit(Unit unit)

    {

      switch (unit)

      {

        case(Unit.Insidious):

          return new Insidious();

        default:

          throw new Exception(String.Format(

            "Unit '{0}' is an invalid Borg Barracks Unit.",

            unit.ToString()));

      }

    }

 

    public override string Name

    {

      get { return "Hive"; }

    }

 

    public override void Upgrade()

    {

      Console.WriteLine("Upgrading the Borg '{0}'.", Name);

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Structures

{

  public class GaeaFederationBarracks : BaseBarracks

  {

    public GaeaFederationBarracks()

      : base()

    {

    }

 

    public override BaseBarracksUnit CreateUnit(Unit unit)

    {

      switch (unit)

      {

        case(Unit.FootmanSoldier):

          return new FootmanSoldier();

        case(Unit.StarshipTrooper):

          return new StarshipTrooper();

        default:

          throw new Exception(String.Format(

            "Unit '{0}' is an invalid Gaea Barracks Unit.",

            unit.ToString()));

      }

    }

 

    public override string Name

    {

      get { return "Playpen"; }

    }

 

    public override void Upgrade()

    {

      Console.WriteLine("Upgrading the Gaea Federation '{0}'.", Name);

    }

  }

}

BaseHQ

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Structures

{

  public abstract class BaseHQ

  {

    public enum Unit

    {

      Ghost,

      Spy

    }

 

    protected BaseHQ()

    {

    }

 

    public abstract BaseHQUnit CreateUnit(Unit unit);

 

    public abstract void Research(Weapon weapon);

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Structures

{

  public class BorgHQ : BaseHQ

  {

    public BorgHQ()

      : base()

    {

    }

 

    public override BaseHQUnit CreateUnit(Unit unit)

    {

      switch(unit)

      {

        case(Unit.Ghost):

          return new Ghost();

        default:

          throw new Exception(String.Format(

            "Unit '{0}' is an invalid Borg HQ Unit.",

            unit.ToString()));

      }

    }

 

    public override void Research(Weapon weapon)

    {

      Console.WriteLine("Researching Borg Weapon '{0}'.", weapon.Name);

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Structures

{

  public class GaeaFederationHQ : BaseHQ

  {

    public GaeaFederationHQ()

      : base()

    {

    }

 

    public override BaseHQUnit CreateUnit(Unit unit)

    {

      switch (unit)

      {

        case (Unit.Spy):

          return new Spy();

        default:

          throw new Exception(String.Format(

            "Unit '{0}' is an invalid Gaea HQ Unit.",

            unit.ToString()));

      }

    }

 

    public override void Research(Weapon weapon)

    {

      Console.WriteLine("Researching Gaea Federation Weapon '{0}'.", weapon.Name);

    }

  }

}

BaseWarRoom

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Structures

{

  public abstract class BaseWarRoom

  {

    public enum Unit

    {

      Admiral,

      Queen

    }

 

    protected BaseWarRoom()

    {

    }

 

    public abstract BaseWarRoomUnit CreateUnit(Unit unit);

 

    public abstract void GetIntel(Location location);

 

    public abstract string Name { get; }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Structures

{

  public class BorgWarRoom : BaseWarRoom

  {

    public BorgWarRoom()

      : base()

    {

    }

 

    public override BaseWarRoomUnit CreateUnit(Unit unit)

    {

      switch(unit)

      {

        case(Unit.Queen):

          return new Queen();

        default:

          throw new Exception(String.Format(

            "Unit '{0}' is an invalid Borg WarRoom Unit.",

            unit.ToString()));

      }

    }

 

    public override void GetIntel(Location location)

    {

      Console.WriteLine("'{0}' is gathering Borg Intel for '{1}'.", Name, location.Name);

    }

 

    public override string Name

    {

      get { return "Temple of Truth"; }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Structures

{

  public class GaeaFederationWarRoom : BaseWarRoom

  {

    public GaeaFederationWarRoom()

      : base()

    {

    }

 

    public override BaseWarRoomUnit CreateUnit(Unit unit)

    {

      switch(unit)

      {

        case(Unit.Admiral):

          return new Admiral();

        default:

          throw new Exception(String.Format(

            "Unit '{0}' is an invalid Gaea WarRoom Unit.",

            unit.ToString()));

      }

    }

 

    public override void GetIntel(Location location)

    {

      Console.WriteLine("'{0}' is gathering Gaea Intel for '{1}'.", Name, location.Name);

    }

 

    public override string Name

    {

      get { return "Games Room"; }

    }

  }

}

The second implementation of a Factory is to make each structure a Factory that exposes a Factory Method to create units. Therefore, we will not have a Factory for each type of unit. Therefore, one can add additional units without having to add additional factories. However, one will be required to modify the relevant Factory Method to return the appropriate structure. The Factory Method that I am referring to can be seen in the diagrams above. The name of the Factory Method is CreateUnit(Unit unit). The Factory Method uses an enumeration to determine what unit it must create. The Units that are available for creation are as follows.

 

BaseBarracksUnit

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

{

  public abstract class BaseBarracksUnit

  {

    protected BaseBarracksUnit()

    {

    }

 

    public abstract void Attack();

 

    public abstract string Name { get; }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

  public class FootmanSoldier : BaseBarracksUnit

  {

    public FootmanSoldier()

      : base()

    {

    }

 

    public override void Attack()

    {

      Console.WriteLine("Footmen Soldier '{0}' attacks.", Name);

    }

 

    public override string Name

    {

      get { return "Incredible Footie"; }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

{

  public class Insidious : BaseBarracksUnit

  {

    public Insidious()

      : base()

    {

    }

 

    public override void Attack()

    {

      Console.WriteLine("Insidious '{0}' attacks.", Name);

    }

 

    public override string Name

    {

      get { return "Slyborg"; }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

{

  public class StarshipTrooper : BaseBarracksUnit

  {

    public StarshipTrooper()

      : base()

    {

    }

 

    public override void Attack()

    {

      Console.WriteLine("Starship Trooper '{0}' attacks.", Name);

    }

 

    public override string Name

    {

      get { return "Spock"; }

    }

  }

}

BaseHQUnit

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

{

  public abstract class BaseHQUnit

  {

    protected BaseHQUnit()

    {

    }

 

    public abstract void UpgradeIntelligence();

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

{

  public class Ghost : BaseHQUnit

  {

    public Ghost()

      : base()

    {

    }

 

    public override void UpgradeIntelligence()

    {

      Console.WriteLine("Upgrading Ghost Intelligence.");

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

{

  public class Spy : BaseHQUnit

  {

    public Spy()

      : base()

    {

    }

 

    public override void UpgradeIntelligence()

    {

      Console.WriteLine("Upgrading Spy Intelligence.");

    }

  }

}

BaseWarRoomUnit

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

{

  public abstract class BaseWarRoomUnit

  {

    protected BaseWarRoomUnit()

    {

    }

 

    public abstract void UpgradeStrategySkill();

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

{

  public class Admiral : BaseWarRoomUnit

  {

    public Admiral()

      : base()

    {

    }

 

    public override void UpgradeStrategySkill()

    {

      Console.WriteLine("Upgrading Admiral Strategy Skill.");

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game.Units

{

  public class Queen : BaseWarRoomUnit

  {

    public Queen()

      : base()

    {

    }

 

    public override void UpgradeStrategySkill()

    {

      Console.WriteLine("Upgrading Queen Strategy Skill.");

    }

  }

}

Furthermore, we have a GameManager class that is also a Factory. GameManager allows one to create the appropriate BaseStructureFactory. The GameManager exposes two 'Factory Properties' that will instantiate the appropriate BaseStructureFactory.

GameManager

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Game

{

  public static class GameManager

  {

    public static BaseStructureFactory BorgStructureFactory

    {

      get { return new BorgStructureFactory(); }

    }

 

    public static BaseStructureFactory GaeaFederationStructureFactory

    {

      get { return new GaeaFederationStructureFactory(); }

    }

  }

}

The code to test the game is as follows:

public static void TestBorgStructureFactory()

{

  BaseStructureFactory structureFactory =

    GameManager.BorgStructureFactory;

 

  BaseBarracks barracks = structureFactory.Barracks;

 

  barracks.Upgrade();

 

  BaseBarracksUnit barracksUnit = barracks.CreateUnit(

    BaseBarracks.Unit.Insidious);

 

  barracksUnit.Attack();

 

  BaseHQ hq = structureFactory.HQ;

 

  hq.Research(new Weapon("Ravanon"));

 

  BaseHQUnit hqUnit = hq.CreateUnit(BaseHQ.Unit.Ghost);

 

  hqUnit.UpgradeIntelligence();

 

  BaseWarRoom warRoom = structureFactory.WarRoom;

 

  warRoom.GetIntel(new Location("Area7"));

 

  BaseWarRoomUnit warRoomUnit = warRoom.CreateUnit(

    BaseWarRoom.Unit.Queen);

 

  warRoomUnit.UpgradeStrategySkill();

 

}

 

public static void TestGaeaFederationStructureFactory()

{

  BaseStructureFactory structureFactory =

    GameManager.GaeaFederationStructureFactory;

 

  BaseBarracks barracks = structureFactory.Barracks;

 

  barracks.Upgrade();

 

  BaseBarracksUnit barracksUnit1 = barracks.CreateUnit(

    BaseBarracks.Unit.FootmanSoldier);

 

  barracksUnit1.Attack();

 

  BaseBarracksUnit barracksUnit2 = barracks.CreateUnit(

    BaseBarracks.Unit.StarshipTrooper);

 

  barracksUnit2.Attack();

 

  BaseHQ hq = structureFactory.HQ;

 

  hq.Research(new Weapon("HammerOfPersuasion"));

 

  BaseHQUnit hqUnit = hq.CreateUnit(BaseHQ.Unit.Spy);

 

  hqUnit.UpgradeIntelligence();

 

  BaseWarRoom warRoom = structureFactory.WarRoom;

 

  warRoom.GetIntel(new Location("Land of the damned"));

 

  BaseWarRoomUnit warRoomUnit = warRoom.CreateUnit(

    BaseWarRoom.Unit.Admiral);

 

  warRoomUnit.UpgradeStrategySkill();

}

The result of the Game Test is as follows:

GameOutput

Example 3 - Insurance

For the last example, we will see how we can use the Abstract Factory within the context of an insurance application. For this example we must assume that we are required to build an insurance application that will allow one to create different type of insurace quotations (Motocar, Building, etc). This application will be used by multiple insurance brokers. Each broker may define different insurance schemes. An insurance quotation must be prepared based on a scheme. The Abstract Factory design for this insurance application is illustrated below.

Click to enlarge

We have two insurance brokers namely 'Chancey' and 'Dodgy' Insurance Brokers. Therefore, we have two factories that will allow one to create the appropriate insurance quotations. The factories are shown below.

InsuranceFactory

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public abstract class BaseInsuranceQuotationFactory

  {

    protected BaseInsuranceQuotationFactory()

    {

    }

 

    public abstract BaseInsuranceQuotation BuildingInsuranceQuotation { get; }

 

    public abstract BaseInsuranceQuotation MotorCarInsuranceQuotation { get; }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public class ChanceyInsuranceBrokers : BaseInsuranceQuotationFactory

  {

    public ChanceyInsuranceBrokers()

      : base()

    {

    }

 

    public override BaseInsuranceQuotation BuildingInsuranceQuotation

    {

      get { return new ChanceyBuildingInsuranceQuotation(); }

    }

 

    public override BaseInsuranceQuotation MotorCarInsuranceQuotation

    {

      get { return new ChanceyMotorCarInsuranceQuotation(); }

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public class DodgyInsuranceBrokers : BaseInsuranceQuotationFactory

  {

    public DodgyInsuranceBrokers()

      : base()

    {

    }

 

    public override BaseInsuranceQuotation BuildingInsuranceQuotation

    {

      get { return new DodgyBuildingInsuranceQuotation(); }

    }

 

    public override BaseInsuranceQuotation MotorCarInsuranceQuotation

    {

      get { return new DodgyMotorCarInsuranceQuotation(); }

    }

  }

}

The BaseInsuranceQuotationFactory defines the interface in terms of the quotations (products) we want to create. For this example we will allow a broker to create two types of insurance quotations namely a Building Quotation and a MotorCar Quotation. The quotations are represented by the following diagram.

InsuranceQuotation

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public abstract class BaseInsuranceQuotation

  {

    protected BaseInsuranceQuotation()

    {

    }

 

    public abstract void Prepare(BaseInsuranceScheme insuranceScheme);

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public abstract class BaseBuildingInsuranceQuotation : BaseInsuranceQuotation

  {

    protected BaseBuildingInsuranceQuotation()

      : base()

    {

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public class ChanceyBuildingInsuranceQuotation : BaseBuildingInsuranceQuotation

  {

    public ChanceyBuildingInsuranceQuotation()

      : base()

    {

    }

 

    public override void Prepare(BaseInsuranceScheme insuranceScheme)

    {

      Console.WriteLine(

        "Preparing the 'Chancey' building insurance quotation for '{0}' scheme.",

        insuranceScheme.Name);

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public abstract class BaseMotorcarInsuranceQuotation : BaseInsuranceQuotation

  {

    protected BaseMotorcarInsuranceQuotation()

      : base()

    {

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public class ChanceyMotorCarInsuranceQuotation : BaseMotorcarInsuranceQuotation

  {

    public ChanceyMotorCarInsuranceQuotation()

      : base()

    {

    }

 

    public override void Prepare(BaseInsuranceScheme insuranceScheme)

    {

      Console.WriteLine(

        "Preparing 'Chancey' motorcar insurance quotation for '{0}' scheme.",

        insuranceScheme.Name);

    }

  }

}

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public class DodgyMotorCarInsuranceQuotation : BaseMotorcarInsuranceQuotation

  {

    public DodgyMotorCarInsuranceQuotation()

      : base()

    {

    }

 

    public override void Prepare(BaseInsuranceScheme insuranceScheme)

    {

      Console.WriteLine(

        "Preparing 'Dodgy' motorcar insurance quotation for '{0}' scheme.",

        insuranceScheme.Name);

    }

  }

}

In order to prepare an insurance quotation, a scheme must be specified. One is able to add as many schemes as one wants for various insurance brokers. This example provides three.

Schemes

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public abstract class BaseInsuranceScheme

  {

    public enum Scheme

    {

      Dee1,

      Jay1,

      Zee1

    }

 

    protected BaseInsuranceScheme()

    {

    }

 

    public abstract string Name { get; }

  }

}

We use another factory, namely InsuranceManager, to create the insurance broker factories.

InsuranceManager

namespace CodeMentor.Patterns.GOF.Creational.AbstractFactory.Examples.Insurance

{

  public static class InsuranceManager

  {

    public static BaseInsuranceQuotationFactory ChanceyInsuranceBrokers

    {

      get { return new ChanceyInsuranceBrokers(); }

    }

 

    public static BaseInsuranceQuotationFactory DodgyInsuranceBrokers

    {

      get { return new DodgyInsuranceBrokers(); }

    }

  }

}

The code to test the Insurance application is as follows:

public static void TestChanceyInsuranceBrokers()

{

  BaseInsuranceQuotationFactory broker = InsuranceManager.ChanceyInsuranceBrokers;

 

  BaseInsuranceQuotation buildingQuote = broker.BuildingInsuranceQuotation;

 

  BaseInsuranceScheme scheme1 = new Dee1InsuranceScheme();

 

  BaseInsuranceScheme scheme2 = new Jay1InsuranceScheme();

 

  BaseInsuranceScheme scheme3 = new Zee1InsuranceScheme();

 

  buildingQuote.Prepare(scheme1);

 

  buildingQuote.Prepare(scheme2);

 

  buildingQuote.Prepare(scheme3);

 

  BaseInsuranceQuotation motorcarQuote = broker.MotorCarInsuranceQuotation;

 

  motorcarQuote.Prepare(scheme1);

 

  motorcarQuote.Prepare(scheme2);

 

  motorcarQuote.Prepare(scheme3);

}

 

public static void TestDodgyInsuranceBrokers()

{

  BaseInsuranceQuotationFactory broker = InsuranceManager.DodgyInsuranceBrokers;

 

  BaseInsuranceQuotation buildingQuote = broker.BuildingInsuranceQuotation;

 

  BaseInsuranceScheme scheme1 = new Dee1InsuranceScheme();

 

  BaseInsuranceScheme scheme2 = new Jay1InsuranceScheme();

 

  BaseInsuranceScheme scheme3 = new Zee1InsuranceScheme();

 

  buildingQuote.Prepare(scheme1);

 

  buildingQuote.Prepare(scheme2);

 

  buildingQuote.Prepare(scheme3);

 

  BaseInsuranceQuotation motorcarQuote = broker.MotorCarInsuranceQuotation;

 

  motorcarQuote.Prepare(scheme1);

 

  motorcarQuote.Prepare(scheme2);

 

  motorcarQuote.Prepare(scheme3);

}

The result from the Insurance application test is as follows:

InsuranceOutput

Example 4 - ADO.NET DatabaseFactory

Represents a set of methods for creating instances of a provider's implementation of the data source classes.

DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");

The DbProviderFactory instance can then be used to create various data objects as follows.

DbProviderFactory

Example 5 - Enterprise Library Data Application Block

The Patterns and Practices group provide a really useful library known as the Enterprise Library. If one is looking for a place to see how patterns have been used effectively, then this is the place to look. Enterprise Library is available at no cost along with source code. I provide one example where a Factory was used. There are many more.

The Patterns and Practices group provide a Factory as part of their Enterprise Library Data Application Block namely DatabaseFactory.

One can then use the DatabaseFactory as follows:

Database database = DatabaseFactory.CreateDatabase();

 

Database database = DatabaseFactory.CreateDatabase("name");

The Data Application Block DatabaseFactory class uses the DbProviderFactory as follows:

Please note that I have used the following code directly from the Enterprise Library Data Application Block

/// <summary>

/// Method for invoking a default Database object. Reads default settings

/// from the ConnectionSettings.config file.

/// </summary>

/// <example>

/// <code>

/// Database dbSvc = DatabaseFactory.CreateDatabase();

/// </code>

/// </example>

/// <returns>Database</returns>

/// <exception cref="System.Configuration.ConfigurationException">

/// <para>An error occured while reading the configuration.</para>

/// </exception>

public static Database CreateDatabase()

{

  try

  {

    DatabaseProviderFactory factory =

      new DatabaseProviderFactory(ConfigurationSourceFactory.Create());

 

    return factory.CreateDefault();

  }

  catch (ConfigurationErrorsException configurationException)

  {

    TryLogConfigurationError(configurationException, "default");

 

    throw;

  }

}

/// <summary>

/// Method for invoking a specified Database service object.  Reads service settings

/// from the ConnectionSettings.config file.

/// </summary>

/// <example>

/// <code>

/// Database dbSvc = DatabaseFactory.CreateDatabase("SQL_Customers");

/// </code>

/// </example>

/// <param name="name">configuration key for database service</param>

/// <returns>Database</returns>

/// <exception cref="System.Configuration.ConfigurationException">

/// <para><paramref name="instanceName"/> is not defined in configuration.</para>

/// <para>- or -</para>

/// <para>An error exists in the configuration.</para>

/// <para>- or -</para>

/// <para>An error occured while reading the configuration.</para>       

/// </exception>

/// <exception cref="System.Reflection.TargetInvocationException">

/// <para>The constructor being called throws an exception.</para>

/// </exception>

public static Database CreateDatabase(string name)

{

  try

  {

    DatabaseProviderFactory factory = new DatabaseProviderFactory(ConfigurationSourceFactory.Create());

    return factory.Create(name);

  }

  catch (ConfigurationErrorsException configurationException)

  {

    TryLogConfigurationError(configurationException, name);

 

    throw;

  }

}

That's all for Article 2. In the next article for this series, we will explore the Singleton (another creational pattern). We will explore the different ways in which one can implement the singleton in C#.NET

By Douglas Minnaar   Popularity  (9513 Views)