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 Delivery {
double price();
String label(String recipient);
}
class AddressDelivery implements Delivery {
@Override
public double price() {
return 5.90;
}
@Override
public String label(String recipient) {
return recipient + ", to the door";
}
}
class OfficeDelivery implements Delivery {
@Override
public double price() {
return 3.90;
}
@Override
public String label(String recipient) {
return recipient + ", at a courier office";
}
}
class LockerDelivery implements Delivery {
@Override
public double price() {
return 2.90;
}
@Override
public String label(String recipient) {
return 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 double checkout(String type, double items) {
Delivery delivery = switch (type) {
case "address" -> new AddressDelivery();
case "office" -> new OfficeDelivery();
case "locker" -> new LockerDelivery();
default -> throw new IllegalArgumentException(type);
};
return items + delivery.price();
}
static double previewPrice(String type) {
Delivery delivery = switch (type) {
case "address" -> new AddressDelivery();
case "office" -> new OfficeDelivery();
case "locker" -> new LockerDelivery();
default -> throw new IllegalArgumentException(type);
};
return delivery.price();
}
static String printLabel(String type, String recipient) {
Delivery delivery = switch (type) {
case "office" -> new OfficeDelivery();
default -> new AddressDelivery();
};
return delivery.label(recipient);
}
System.out.printf("Order: €%.2f%n", checkout("locker", 40));
System.out.printf("Preview: delivery €%.2f%n", previewPrice("locker"));
System.out.println("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 default 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 Delivery 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

class Deliveries {
public static Delivery create(String type) {
return switch (type) {
case "address" -> new AddressDelivery();
case "office" -> new OfficeDelivery();
case "locker" -> new LockerDelivery();
default -> throw new IllegalArgumentException("Unknown delivery type: " + type);
};
}
}

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

static double checkout(String type, double items) {
return items + Deliveries.create(type).price();
}
static double previewPrice(String type) {
return Deliveries.create(type).price();
}
static String printLabel(String type, String recipient) {
return Deliveries.create(type).label(recipient);
}
System.out.printf("Order: €%.2f%n", checkout("locker", 40));
System.out.printf("Preview: delivery €%.2f%n", previewPrice("locker"));
System.out.println("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: Map<String, Supplier<Delivery>> with values like AddressDelivery::new. 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, and the compiler checks that there is a default (see the card "Unknown type").

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 implements Delivery {
@Override
public double price() {
return 0.00;
}
@Override
public String label(String recipient) {
return recipient + ", at the store";
}
}

and one line in the factory, highlighted here:

class Deliveries {
public static Delivery create(String type) {
return switch (type) {
case "address" -> new AddressDelivery();
case "office" -> new OfficeDelivery();
case "locker" -> new LockerDelivery();
case "pickup" -> new PickupDelivery();
default -> throw new IllegalArgumentException("Unknown delivery type: " + type);
};
}
}
Delivery home = Deliveries.create("address");
System.out.printf("%s: €%.2f%n", home.label("Maria"), home.price());
Delivery locker = Deliveries.create("locker");
System.out.printf("%s: €%.2f%n", locker.label("Maria"), locker.price());
Delivery pickup = Deliveries.create("pickup");
System.out.printf("%s: €%.2f%n", pickup.label("Maria"), pickup.price());
Output
Maria, to the door: €5.90
Maria, at a locker: €2.90
Maria, at the store: €0.00

Delivery home = Deliveries.create("address");
System.out.printf("%s: €%.2f%n", home.label("Maria"), home.price());
Delivery locker = Deliveries.create("locker");
System.out.printf("%s: €%.2f%n", locker.label("Maria"), locker.price());
Delivery pickup = Deliveries.create("pickup");
System.out.printf("%s: €%.2f%n", pickup.label("Maria"), pickup.price());
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 Delivery: 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 {
Delivery delivery = Deliveries.create("drone");
System.out.println(delivery.label("Maria"));
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
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 default? Java doesn't let you forget it: a switch expression has to cover every value, or the program doesn't compile:
class Deliveries {
public static Delivery create(String type) {
return switch (type) {
case "address" -> new AddressDelivery();
case "office" -> new OfficeDelivery();
case "locker" -> new LockerDelivery();
};
}
}
Error
error: the switch expression does not cover all possible input values

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 ReportFile {
void add(String product, double total);
String text();
}
class CsvFile implements ReportFile {
private final List<String> lines = new ArrayList<>(List.of("product;total"));
@Override
public void add(String product, double total) {
lines.add(product + ";" + String.format("%.2f", total));
}
@Override
public String text() {
return String.join("\n", lines);
}
}
class HtmlFile implements ReportFile {
private final List<String> rows = new ArrayList<>();
@Override
public void add(String product, double total) {
rows.add("<tr><td>" + product + "</td><td>" + String.format("%.2f", total) + "</td></tr>");
}
@Override
public String text() {
return "<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, double total) {}
abstract class ReportExporter {
protected abstract ReportFile createFile();
public String export(List<Sale> sales) {
ReportFile file = createFile();
double overall = 0;
for (Sale sale : sales) {
file.add(sale.product(), sale.total());
overall += sale.total();
}
file.add("Total", overall);
return file.text();
}
}
class CsvExporter extends ReportExporter {
@Override
protected ReportFile createFile() {
return new CsvFile();
}
}
class HtmlExporter extends ReportExporter {
@Override
protected ReportFile createFile() {
return new HtmlFile();
}
}
List<Sale> sales = List.of(new Sale("Coffee", 12.50), new Sale("Tea", 8.00));
System.out.println(new CsvExporter().export(sales));
System.out.println(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 the constructor as a Supplier<ReportFile>: new ReportExporter(CsvFile::new).

Try it

Predict the output

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

String[] types = { "office", "Locker", "locker" };
for (String type : types) {
try {
System.out.println(Deliveries.create(type).label("Maria"));
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
Answer

Try it

Find the bug

Another developer wrote the factory like this:

class Deliveries {
public static Delivery create(String type) {
return switch (type) {
case "office" -> new OfficeDelivery();
case "locker" -> new LockerDelivery();
default -> new AddressDelivery();
};
}
}

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

Delivery delivery = Deliveries.create("Locker");
System.out.println("Label: " + delivery.label("Maria"));
System.out.printf("Delivery: €%.2f%n", delivery.price());
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 ·