Abstract Factory Design Pattern
Theory
Abstract Factory provides interface for creating set of objects without specifying concrete implementation. Abstract Factory uses abstract classes and decouples concrete implementation from the system. It brings flexibility to easily wire up different set of objects based on specified abstraction.
Abstract Factory helps to implement scenario where different set of products have the same implementation in application.
Diagram
Client class instead of pointing to concrete set of products {ConcreteProductA1, ConcreteProductB1} or {ConcreteProductA2, ConcreteProductB2} works with theirs abstraction {ProductABase, ProductBBase}.
Concrete factories are responsible for creating set of concrete products.
Implementation
ProductA
public abstract class ProductABase
{
public abstract void MethodA();
}
public sealed class ConcreteProductA1 : ProductABase
{
public override void MethodA()
{
Console.WriteLine("MethodA of A1 has been called");
}
}
public sealed class ConcreteProductA2 : ProductABase
{
public override void MethodA()
{
Console.WriteLine("MethodA of A2 has been called");
}
}
ProductB
public abstract class ProductBBase
{
public abstract void MethodB();
}
public sealed class ConcreteProductB1 : ProductBBase
{
public override void MethodB()
{
Console.WriteLine("MethodB on B1 has been started.");
}
}
public sealed class ConcreteProductB2 : ProductBBase
{
public override void MethodB()
{
Console.WriteLine("MethodB on B2 has been started.");
}
}
Factories
To prevent user for making mistake in passing wrong set of products we will introduce factories which will be responsible for creating correct combination of products.
Each Factory ensures that only specified pair of products will be instantiated and set to the Client.
public abstract class FactoryBase
{
public abstract ProductABase CreateProductA();
public abstract ProductBBase CreateProductB();
}
public class ConcreteFactory1 : FactoryBase
{
public override ProductABase CreateProductA()
{
return new ConcreteProductA1();
}
public override ProductBBase CreateProductB()
{
return new ConcreteProductB1();
}
}
public class ConcreteFactory2 : FactoryBase
{
public override ProductABase CreateProductA()
{
return new ConcreteProductA2();
}
public override ProductBBase CreateProductB()
{
return new ConcreteProductB2();
}
}
Client
public sealed class Client
{
private ProductABase _productA;
private ProductBBase _productB;
public Client(FactoryBase _factory)
{
_productA = _factory.CreateProductA();
_productB = _factory.CreateProductB();
}
public void ExecuteProducts()
{
_productA.MethodA();
_productB.MethodB();
}
}
Usage
This is how it used in application
FactoryBase factory = new ConcreteFactory1();
Client client = new Client(factory);
client.ExecuteProducts();
This is an output of above code execution:
It is enough to change just one line of code and we will get implementation of another set of products.
FactoryBase factory = new ConcreteFactory2();
...
...
Example
Use case scenario
We have a hardware department inside of the company which delivers products for employees needs: desktop boxes, monitors, mice, keyboard, etc. They also builds and delivers a Computer set (bundle of products).
Due to fact that we have various products of the same type, we can make many Computers with different combination of products.
To simplify our example lets assume that Computer contains only desktop box and monitor.
UML Diagram
This is the UML diagram which describes Computer set thru Abstract Factory model.
Implementation of Monitors
MonitorBase class represents a shell for all existing monitors. It contains just one member:
- MonitorSize - abstract readonly property which returns size of monitor in inches
There are two types of monitors classes used in our example:
public abstract class MonitorBase
{
public abstract int MonitorSize { get;}
}
public sealed class LCDDell19 : MonitorBase
{
public override int MonitorSize
{
get { return 19; }
}
}
public sealed class LCDSamsung21 : MonitorBase
{
public override int MonitorSize
{
get { return 21; }
}
}
Implementation of Desktop boxes
DesktopBoxBase class represents all desktop boxes. It contains a couple of members:
- CPU - abstract readonly property which returns type of CPU used in desktop box
- HDD - abstract readonly propery which returns type and size of hard drive used in box
- Memory - abstract readonly property which returns type and size of memory used in box
- Start - abstract method
- Shutdown - abstract method
In this example we will use two type of boxes. To simplify example we will call them AMDBox and IntelBox. Both classes are inherited from DesktopBoxBase and contain implementation of members we listed above.
public abstract class DesktopBoxBase
{
public abstract string CPU{ get;}
public abstract string Memory{ get;}
public abstract string HDD { get;}
public abstract void Start();
public abstract void Shutdown();
}
public sealed class AMDBox : DesktopBoxBase
{
public override void Start()
{
Console.WriteLine("AMDBox has been started.");
}
public override void Shutdown()
{
Console.WriteLine("AMDBox has been shutted down.");
}
public override string CPU
{
get { return "AMD 2800+"; }
}
public override string Memory
{
get { return "DDR 2x512Mb";}
}
public override string HDD
{
get { return "SATA 2x160Gb";}
}
}
public sealed class IntelBox : DesktopBoxBase
{
public override void Start()
{
Console.WriteLine("IntelBox has been started.");
}
public override void Shutdown()
{
Console.WriteLine("IntelBox box has been shutted down.");
}
public override string CPU
{
get { return "Intel Core2Quad"; }
}
public override string Memory
{
get { return "DDR 2x1024Mb"; }
}
public override string HDD
{
get { return "SATA 250Gb"; }
}
}
Implementation of Factory
As you can see in diagram, there is a abstract ComputerFactory class which contains two abstract methods which are responsible for creating concrete products for a particular set: CreateComputerBox and CreateMonitor.
There is one to one relationship between product set (or bundle) and concrete factory. Each concrete factory responsible for delivering appropriate products to Computer class.
public abstract class ComputerFactory
{
public abstract DesktopBoxBase CreateComputerBox();
public abstract MonitorBase CreateMonitor();
}
public sealed class DeveloperPC : ComputerFactory
{
public override DesktopBoxBase CreateComputerBox()
{
return new IntelBox();
}
public override MonitorBase CreateMonitor()
{
return new LCDDell19();
}
}
public sealed class ManagerPC : ComputerFactory
{
public override DesktopBoxBase CreateComputerBox()
{
return new AMDBox();
}
public override MonitorBase CreateMonitor()
{
return new LCDSamsung21();
}
}
Computer class
public sealed class Computer
{
private DesktopBoxBase _box;
public DesktopBoxBase DesktopBox
{
get { return _box; }
}
private MonitorBase _monitor;
public MonitorBase Monitor
{
get { return _monitor; }
}
public Computer(ComputerFactory cf)
{
_box = cf.CreateComputerBox();
_monitor = cf.CreateMonitor();
}
public void WriteOutConfiguration()
{
Console.WriteLine("CPU: {0}", _box.CPU);
Console.WriteLine("Memory: {0}", _box.Memory);
Console.WriteLine("HDD, {0}", _box.HDD);
}
public void Start()
{
_box.Start();
}
public void Shutdown()
{
_box.Shutdown();
}
}
Usage
ComputerFactory factory = new DeveloperPC();
Computer computer = new Computer(factory);
Console.WriteLine("Size of display is {0} inches",
computer.Monitor.MonitorSize); computer.WriteOutConfiguration();
computer.DesktopBox.Start();
computer.DesktopBox.Shutdown();
It is enough to change just one line of code and we will get functionality of another computer:
ComputerFactory factory = new ManagerPC();
Conclusion
Advantages
- Without touching implementation we can add new set of products. We just need to implement new concrete factory and instantiate it instead of existing.
Disadvantages.
- All products of the same type should contain the same members. It is not possible (or too problematic) to add and use new member for only one product. For instance if we need Dell19 monitors to have additional "Color" property we have to implement it for all type for monitors inherited from MonitorBase class.
- All factories responsible to build same type of products. For instance we used factory to build Computer with "desktop + monitor" set of products. We cannot add factory which will have "desktop + monitor + mouse". Either all factories should build "desktop + monitor + mouse" or all should build "desktop + monitor".