Skip to content
wannacode

Factory Method and Simple Factory

In short: A factory keeps the decision of which class to create in one place: Simple Factory holds the choice in one spot, and Factory Method lets a subclass decide which object to make.

Problem

Three places know every delivery type

The order says how it gets to the customer: "address" (to the door), "office" (at a courier office) or "locker" (at a parcel locker). Every type has a class with its own price and its own label:

interface IDelivery
{
decimal Price { get; }
string Label(string recipient);
}
class AddressDelivery : IDelivery
{
public decimal Price => 5.90m;
public string Label(string recipient) => $"{recipient}, to the door";
}
class OfficeDelivery : IDelivery
{
public decimal Price => 3.90m;
public string Label(string recipient) => $"{recipient}, at a courier office";
}
class LockerDelivery : IDelivery
{
public decimal Price => 2.90m;
public string Label(string recipient) => $"{recipient}, at a locker";
}

Three parts of the shop need the delivery: the checkout, the price preview and the label printer. Each one decides by itself which class to create:

static decimal Checkout(string type, decimal items)
{
IDelivery delivery = type switch
{
"address" => new AddressDelivery(),
"office" => new OfficeDelivery(),
"locker" => new LockerDelivery(),
_ => throw new ArgumentException(type),
};
return items + delivery.Price;
}
static decimal PreviewPrice(string type)
{
IDelivery delivery = type switch
{
"address" => new AddressDelivery(),
"office" => new OfficeDelivery(),
"locker" => new LockerDelivery(),
_ => throw new ArgumentException(type),
};
return delivery.Price;
}
static string PrintLabel(string type, string recipient)
{
IDelivery delivery = type switch
{
"office" => new OfficeDelivery(),
_ => new AddressDelivery(),
};
return delivery.Label(recipient);
}
Console.WriteLine($"Order: €{Checkout("locker", 40):F2}");
Console.WriteLine($"Preview: delivery €{PreviewPrice("locker"):F2}");
Console.WriteLine("Label: " + PrintLabel("locker", "Maria"));
Output
Order: €42.90
Preview: delivery €2.90
Label: Maria, to the door

Lockers arrived last month. Someone added "locker" to the checkout and to the preview. The label printer was written before that and has an _ for the address. Maria paid for delivery to a locker, and the courier went to her address.

A new type is three changes, and one gets forgotten. The choice "which class for which type" is copied, and the copies don't know about each other.

The idea

Simple Factory: new lives in one place

The choice "which class for which type" is one decision, so it lives in one place: a factory. It gets the type and returns a new object. The caller doesn't write new and doesn't know the class: it gets the interface.

The roles:

  • the product: the common shape, the IDelivery interface;
  • the concrete products: AddressDelivery, OfficeDelivery, LockerDelivery;
  • the factory: Deliveries.Create(type), the only place that knows which class goes with which type;
  • the clients: the checkout, the preview, the label. They only ask.

This is called Simple Factory. It is not from the Gang of Four book, but it is the most common form. Factory Method from the book is a slightly different thing and comes later in the lesson.

One rule: an unknown type is an error, not a silent default. A factory that doesn't know the type throws an exception with a clear message. Otherwise the error doesn't go away, it leaves with the courier for the wrong address.

The "name → strategy" table from the Strategy lesson is the same thing: a factory that gives you an object for a piece of text.

See it

One factory for the three places

static class Deliveries
{
public static IDelivery Create(string type) => type switch
{
"address" => new AddressDelivery(),
"office" => new OfficeDelivery(),
"locker" => new LockerDelivery(),
_ => throw new ArgumentException($"Unknown delivery type: {type}"),
};
}

The checkout, the preview and the label no longer choose, they ask:

static decimal Checkout(string type, decimal items)
{
return items + Deliveries.Create(type).Price;
}
static decimal PreviewPrice(string type)
{
return Deliveries.Create(type).Price;
}
static string PrintLabel(string type, string recipient)
{
return Deliveries.Create(type).Label(recipient);
}
Console.WriteLine($"Order: €{Checkout("locker", 40):F2}");
Console.WriteLine($"Preview: delivery €{PreviewPrice("locker"):F2}");
Console.WriteLine("Label: " + PrintLabel("locker", "Maria"));
Output
Order: €42.90
Preview: delivery €2.90
Label: Maria, at a locker

The label is for a locker. The choice is in one place, so it can't be right in the checkout and wrong on the label.

More precisely, if you are curious

The factory can also be a table: Dictionary<string, Func<IDelivery>> with values like () => new AddressDelivery(). A table is handy when the types are added from outside (every module registers its own). A switch is clearer when all the types are known in one place.

See it

A new type: one class and one line

The shop adds store pickup: type "pickup", free of charge. What's new is one class:

class PickupDelivery : IDelivery
{
public decimal Price => 0.00m;
public string Label(string recipient) => $"{recipient}, at the store";
}

and one line in the factory, highlighted here:

static class Deliveries
{
public static IDelivery Create(string type) => type switch
{
"address" => new AddressDelivery(),
"office" => new OfficeDelivery(),
"locker" => new LockerDelivery(),
"pickup" => new PickupDelivery(),
_ => throw new ArgumentException($"Unknown delivery type: {type}"),
};
}
IDelivery home = Deliveries.Create("address");
Console.WriteLine($"{home.Label("Maria")}: €{home.Price:F2}");
IDelivery locker = Deliveries.Create("locker");
Console.WriteLine($"{locker.Label("Maria")}: €{locker.Price:F2}");
IDelivery pickup = Deliveries.Create("pickup");
Console.WriteLine($"{pickup.Label("Maria")}: €{pickup.Price:F2}");
Output
Maria, to the door: €5.90
Maria, at a locker: €2.90
Maria, at the store: €0.00

IDelivery home = Deliveries.Create("address");
Console.WriteLine($"{home.Label("Maria")}: €{home.Price:F2}");
IDelivery locker = Deliveries.Create("locker");
Console.WriteLine($"{locker.Label("Maria")}: €{locker.Price:F2}");
IDelivery pickup = Deliveries.Create("pickup");
Console.WriteLine($"{pickup.Label("Maria")}: €{pickup.Price:F2}");
Step by step, in words
  1. Deliveries.Create("address") gets only the text from the order. The factory looks for the mold that matches it.
  2. A new AddressDelivery comes out of the mold, but home has the type IDelivery: the caller sees only Price and Label().
  3. The same factory, a different text: "locker" goes past the other molds and stops at LockerDelivery.
  4. The label says locker now: the class is picked in one place, and the checkout, the preview and the label all ask that place.
  5. A new type: the class PickupDelivery and one more line in the factory. The lines that call Deliveries.Create don't change.
  6. "pickup" goes through like any other type. The new type was added in one place.

The checkout, the preview and the label are not touched. They learn about the new type when an order asks for it.

See it

Unknown type

The type comes from outside: from a form, from a partner's site, from an old file. Sooner or later a type shows up that isn't there:

try
{
IDelivery delivery = Deliveries.Create("drone");
Console.WriteLine(delivery.Label("Maria"));
}
catch (ArgumentException e)
{
Console.WriteLine(e.Message);
}
Output
Unknown delivery type: drone

The error says which type is unknown. It is caught where the order comes into the shop, and the customer sees a message instead of a parcel at the wrong place.

And if the factory has no _? C# only gives a warning. The program compiles and crashes when an unknown type arrives:
static class Deliveries
{
public static IDelivery Create(string type) => type switch
{
"address" => new AddressDelivery(),
"office" => new OfficeDelivery(),
"locker" => new LockerDelivery(),
};
}
Error
warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '""' is not covered.
Unhandled exception. System.Runtime.CompilerServices.SwitchExpressionException: Non-exhaustive switch expression failed to match its input.
Unmatched value was drone.

See it

Factory Method: the subclass decides

Simple Factory chooses by text. Sometimes the choice isn't by text, but by which object is asking.

The sales report is sent as CSV (for accounting) and as an HTML table (in an email). Both do the same thing: every product is a row, and at the end there is a "Total" row. Only the file the rows are written to is different:

interface IReportFile
{
void Add(string product, decimal total);
string Text();
}
class CsvFile : IReportFile
{
private readonly List<string> lines = new() { "product;total" };
public void Add(string product, decimal total) => lines.Add($"{product};{total:F2}");
public string Text() => string.Join("\n", lines);
}
class HtmlFile : IReportFile
{
private readonly List<string> rows = new();
public void Add(string product, decimal total) => rows.Add($"<tr><td>{product}</td><td>{total:F2}</td></tr>");
public string Text() => "<table>\n" + string.Join("\n", rows) + "\n</table>";
}
The shared algorithm is in the base class. The step "make a file" is an abstract method that every subclass provides:
record Sale(string Product, decimal Total);
abstract class ReportExporter
{
protected abstract IReportFile CreateFile();
public string Export(List<Sale> sales)
{
IReportFile file = CreateFile();
decimal overall = 0;
foreach (var sale in sales)
{
file.Add(sale.Product, sale.Total);
overall += sale.Total;
}
file.Add("Total", overall);
return file.Text();
}
}
class CsvExporter : ReportExporter
{
protected override IReportFile CreateFile() => new CsvFile();
}
class HtmlExporter : ReportExporter
{
protected override IReportFile CreateFile() => new HtmlFile();
}
var sales = new List<Sale> { new("Coffee", 12.50m), new("Tea", 8.00m) };
Console.WriteLine(new CsvExporter().Export(sales));
Console.WriteLine(new HtmlExporter().Export(sales));
Output
product;total
Coffee;12.50
Tea;8.00
Total;20.50
<table>
<tr><td>Coffee</td><td>12.50</td></tr>
<tr><td>Tea</td><td>8.00</td></tr>
<tr><td>Total</td><td>20.50</td></tr>
</table>

This is Factory Method: the base class calls a method that creates the object (CreateFile()), and the subclass decides which class exactly.

Why not pass in a ready file? The file collects rows. Every Export has to start with an empty file, so the report doesn't keep a file, it knows how to make a new one.

When is Simple Factory enough? Factory Method pays off when there is a shared algorithm around the creation that the subclasses shouldn't repeat (here: the rows and the total). If you only want an object by type, with no algorithm around it, Simple Factory is clearer. And a subclass that exists only to return a new object is ceremony. Then pass a Func<IReportFile>: new ReportExporter(() => new CsvFile()).

Try it

Predict the output

The types come from a form on the site. What does the program print?

string[] types = { "office", "Locker", "locker" };
foreach (var type in types)
{
try
{
Console.WriteLine(Deliveries.Create(type).Label("Maria"));
}
catch (ArgumentException e)
{
Console.WriteLine(e.Message);
}
}
Answer

Try it

Find the bug

Another developer wrote the factory like this:

static class Deliveries
{
public static IDelivery Create(string type) => type switch
{
"office" => new OfficeDelivery(),
"locker" => new LockerDelivery(),
_ => new AddressDelivery(),
};
}

The order comes from a partner's site, which sends the type with a capital letter:

IDelivery delivery = Deliveries.Create("Locker");
Console.WriteLine("Label: " + delivery.Label("Maria"));
Console.WriteLine($"Delivery: €{delivery.Price:F2}");
Output
Label: Maria, to the door
Delivery: €5.90

Maria chose a locker. She paid €5.90 for delivery instead of €2.90, and the parcel went to her address. Which line is the bug on?

Pick the line with the bug.

The idea

When you don't need it

A factory is one more jump when you read: to see which class gets created, you go somewhere else. It isn't worth it when:

  • that place always needs the same class. new OfficeDelivery() where the delivery always goes to an office is clearer than a factory;
  • the type is known while you write the code, and doesn't come in as data (from an order, a file, a form);
  • there is a factory for the factory. DeliveryFactoryFactory almost always means one of the two is not needed;
  • a Factory Method has one subclass "for the future".

It pays off when the type comes in as data, and the choice of the class would otherwise be copied in several places. Or when there is a shared algorithm around the creation, and only the step "which object" is different.

Exercise

Express delivery

Solve this in your editor.

Express delivery. The shop adds express delivery to the door: type "express", €9.90, label "…, express to the door". Add an ExpressDelivery class and make it available through the factory. The checkout, the preview and the label are not touched. Create an express delivery for Maria and print it like in the card "A new type":

Output
Maria, express to the door: €9.90

More tasks

  • Capital letters and spaces. The form sends "Locker" and " locker ". Make the factory accept them, changing only the factory. Unknown types must still give an error.
  • A JSON report. Add a JSON format to the report from the card "Factory Method": [{"product": "Coffee", "total": 12.50}, …]. Which classes do you write, and which don't you touch?
  • Price by weight. Lockers take parcels up to 20 kg, and over 5 kg delivery to the door costs more. Where does the weight go: into the factory or into the price method? Think about why.

Test

Check yourself

1. The shop adds drone delivery, type "drone". What do you write with Simple Factory?
2. The factory gets a type it does not know. What should it do?
3. When is Factory Method better than Simple Factory?
4. To save memory, the factory keeps one object for each type and returns it on every create. The object keeps the recipient in a field. What happens with two locker orders in a row?
5. When don't you need a factory?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·