Design Patterns - Factory and Abstract Factory

What is Factory Pattern

A factory is an object for creating objects

Factory Pattern Variations

  • Simple Factory
  • Factory Method
  • Abstract Factory

Factory Pattern Characteristics

  • Client: Asks for a created product
    • Shopping cart
  • Creator: Facilitates a creation
    • ShippingProviderFactory
  • Product: The product of the creation
    • ShippingProvider Instance

The Client no longer needs to know how to create an object or exactly what flavor of that class it will use

Simple Factory Example

We have a ShoppingCart Class and inside this Class we create a shippingProvider object. It will create different shippingProvider based on order’s sender country

1
2
3
4
5
6
7
8
9
10
11
12
if (order.Sender.Country == "Australia")
{
//Australia Post Shipping Provider
}
else if (order.Sender.Country == "Sweden")
{
//Swedish Postal Service Shipping Provider
}
else
{
throw new NotSupportedException("No shipping provider found for origin country");
}

But the shippingProvider object should not be created inside the ShoppingCart Class, ShoppingCart Class should just ask a ShippingProviderFactory Class for a shippingProvider object, and it will be provided one.

So we should moved the code to a new ShippingProviderFactory Class and invoke this class’s Creation method.

1
var shippingProvider = ShippingProviderFactory.CreateShippingProvider(order.Sender.Country);

One problem is not we are still hardcoding the Country inside our ShippingProviderFactory Class. We should add another layer of abstraction between the ShippingProviderFactory and the implementation of the ShippingProvider.

Factory Method

The Factory Method Pattern is introduced to allow for a flexible and extensible application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class ShippingProviderFactory {
public abstract ShippingProvider CreateShippingProvider(string country);

public ShippingProvider GetShippingProvider(string country) {
var provider = CreateShippingProvider(country)

// we may want to do some common changes on the shippingProvider created
// before we return it back to the caller (ShoppingCart)
if (country == "Sweden" && provider.InsuranceOptions.ProviderHasInsurance)
{
provider.RequireSignature = false;
}
return provider;
}
}

It contains two methods.

The CreateShippingProvider() method will be implemented by its subclasses with different implementations.

The GetShippingProvider() method will allow user to decide what’s passed into the creation. And it allows user to do additional common interactions with the result of the creation before it’s being passed back to the caller(ShoppingCart).

Now we can create different implementations of the creation of a shippingProvider based on the input parameter(country).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StandardShippingProviderFactory : ShippingProviderFactory
{
public override ShippingProvider CreateShippingProvider(string country)
{
return new StandardShippingProviderFactory();
}
}

public class GlobalExpressShippingProviderFactory : ShippingProviderFactory
{
public override ShippingProvider CreateShippingProvider(string country)
{
return new GlobalExpressShippingProvider();
}
}

In the caller Class (ShoppingCart) we can inject ShippingProviderFactory

1
2
3
4
5
6
// inject ShippingProviderFactory into the ShoppingCart Constructor
public ShoppingCart(Order order, ShippingProviderFactory shippingProviderFactory)
{
this.order = order;
this.shippingProviderFactory = shippingProviderFactory;
}

Also compose the ShippingProviderFactory object on app start

1
var cart = new ShoppingCart(order, new StandardShippingProviderFactory());

Abstract Factory Pattern

The abstract factory pattern provides a way to encapsulete a group of individual factories that have a common theme without specifying their concrete classes.

It adds another layer of abstraction which allow users to choose which factory to use on app start.

Different factories have the same methods but with different implementations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public interface IPurchaseProviderFactory
{
ShippingProvider CreateShippingProvider(Order order);
IInvoice CreateInvoice(Order order);
ISummary CreateSummary(Order order);

}

public class AustraliaPurchaseProviderFactory : IPurchaseProviderFactory
{
public IInvoice CreateInvoice(Order order)
{
return new GSTInvoice();
}

public ShippingProvider CreateShippingProvider(Order order)
{
var shippingProviderFactory = new StandardShippingProviderFactory();

return shippingProviderFactory.GetShippingProvider(order.Sender.Country);
}

public ISummary CreateSummary(Order order)
{
return new CSVSummary();
}
}

public class SwedenPurchaseProviderFactory : IPurchaseProviderFactory
{
public IInvoice CreateInvoice(Order order)
{
if (order.Recipient.Country != order.Sender.Country)
{
return new NoVATInvoice();
}
return new VATInvoice();
}

public ShippingProvider CreateShippingProvider(Order order)
{
ShippingProviderFactory shippingProviderFactory;

if (order.Sender.Country != order.Recipient.Country)
{
shippingProviderFactory = new GlobalExpressShippingProviderFactory();
}
else
{
shippingProviderFactory = new StandardShippingProviderFactory();
}

return shippingProviderFactory.GetShippingProvider(order.Sender.Country);
}

public ISummary CreateSummary(Order order)
{
return new EmailSummary();
}
}

Client (ShoppingCart) Class doesn’t need to know which factory to use, it just needs to know when to create a product using the factory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public ShoppingCart(Order order, IPurchaseProviderFactory purchaseProviderFactory)  
{
this.order = order;
this.purchaseProviderFactory = purchaseProviderFactory;
}

public string Finalize()
{
var shippingProvider = purchaseProviderFactory.CreateShippingProvider(order);

var invoice = purchaseProviderFactory.CreateInvoice(order);

var summary = purchaseProviderFactory.CreateSummary(order);

summary.Send();

order.ShippingStatus = ShippingStatus.ReadyForShippment;

return shippingProvider.GenerateShippingLabelFor(order);
}

The concrete factory object will be instantiated on app starts(or based on user input).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IPurchaseProviderFactory purchaseProviderFactory;

if (order.Sender.Country == "Sweden")
{
purchaseProviderFactory = new SwedenPurchaseProviderFactory();
}
else if (order.Sender.Country == "Australia")
{
purchaseProviderFactory = new AustraliaPurchaseProviderFactory();
}
else
{
throw new Exception("Country not supported.");
}

var cart = new ShoppingCart(order, purchaseProviderFactory);

Factory Pattern in Testing

Extract creation of mocked, facked or commonly oused intances in tests.

We could use the Factory Pattern in our Unit Tests. it will be easier to test the parts that use them as you can inhect faked or mocked implementations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public abstract class OrderFactory
{
protected abstract Order CreateOrder();

public Order GetOrder()
{
var order = CreateOrder();

order.LineItems.Add(
new Item("testA", "testB", 100m), 1
);

order.LineItems.Add(
new Item("TestC", "TestD", decimal.MaxValue), 1
);

return order;
}
}

public class StandardOrderFactory : OrderFactory
{
protected override Order CreateOrder()
{
var order = new Order
{
Recipient = new Address
{
To = "Yuan",
Country = "Australia"
},
Sender = new Address
{
To = "Someone else",
Country = "Australia"
}
};

return order;
}
}

public class InternationalOrderFactory : OrderFactory
{
protected override Order CreateOrder()
{
var order = new Order
{
Recipient = new Address
{
To = "Yuan",
Country = "Australia"
},
Sender = new Address
{
To = "Someone else",
Country = "Sweden"
}
};

return order;
}
}

Summary

  • Separates the client(ShoppingCart) from the creation

  • Introduce subclasses (StandardShippingProviderFactory, GlobalExpressShippingProviderFactory) and concrete implementations to add functionality.

  • Factory Pattern is very common when writing tests