Embercode
✕
Object-Oriented ProgrammingLesson 14 of 17

45 min read

Factory and builder

Why this matters

Every object starts with a constructor, which is a function that runs when the object is created. A constructor has the same name as its class, and it cannot have a name of its own. Its parameters are the only way to tell it what to do. This works well for small classes with two or three fields. It becomes hard to read when the input has more than one meaning or when many fields are optional.

Two problems come up again and again. The first is that two different ways of creating a value may need the same parameter types. A temperature given in Celsius and a temperature given in Fahrenheit are both a double, so the two constructors clash. The second is that a class with many optional fields needs many constructors, one for each combination of fields. A call such as ServerConfig("example.com", 443, 30, 3, true) is also hard to read, because the reader cannot see what each number means.

Two design patterns solve these problems. A factory method is a static function that creates and returns an object, and it has its own name. A builder is a separate object that collects settings one at a time and then creates the final object. This lesson shows one example of each, a harder example of the factory returning a subtype, and a common set of mistakes.

Some terms used below:

  • Static member: a function or field that belongs to the class itself, not to one object. It is called with the class name and ::, such as Temperature::from_celsius(20.0).
  • Factory method: a static function that builds an object and returns it. The caller uses the function name to say what kind of object it wants.
  • Builder: an object whose only job is to collect settings and produce another object. Its setter functions usually return the builder itself, so calls can be chained on one line.
  • Pointer: a value that holds the memory address of an object. A std::unique_ptr is a pointer that deletes the object it owns when it goes out of scope, so the caller does not need to delete it.

Factory method: a named way to create a value

Start with the clash described above. A class holds a temperature in Celsius. The caller may have a value in Celsius or in Fahrenheit. The two constructors would both take a double:

class Temperature {
public:
    Temperature(double celsius) : celsius_(celsius) {}
    Temperature(double fahrenheit) : celsius_((fahrenheit - 32.0) * 5.0 / 9.0) {}
 
private:
    double celsius_;
};

The compiler rejects this class with the following error:

error: 'Temperature::Temperature(double)' cannot be overloaded with 'Temperature::Temperature(double)'
note: previous declaration 'Temperature::Temperature(double)'

A constructor is identified by its parameter types, and both of these take one double. Changing the parameter names does not help, because names are not part of the signature.

The fix is to give each way of creating a value its own static function. The constructor becomes private, so callers must use the named functions. Here is the factory version.

C++ · editable

Expected output:

a: 20.0 C
b: 20.0 C

Each factory has a name that says which unit it expects. from_fahrenheit(68.0) is clear to anyone who reads the call, and the conversion is written in one place, inside the class. Both factories call the private constructor, which is allowed because they are members of the same class. The private constructor is the key part. If a caller tries the constructor directly, with the Temperature class above, the compiler stops:

int main() {
    Temperature t = Temperature(20.0);
    return static_cast<int>(t.celsius());
}
error: 'Temperature::Temperature(double)' is private within this context

Because the constructor is private, the only way to get a Temperature is through a named factory. The class can then guarantee that every object was made by one of its own functions. The Invariants and validation lesson earlier in this module uses the same idea to keep objects valid.

Factory method that returns a subtype

A factory does not have to return the same class every time. It can look at its input and return an object of one of several derived classes. The caller receives the base type and uses it without knowing which class was built.

This example has a Shape base class with two derived classes, Square and Circle. The static function Shape::make picks one based on a string. It returns a std::unique_ptr<Shape>. That type owns the new object. When the pointer goes out of scope, the object is deleted, so the caller does not need a matching delete.

C++ · editable

Expected output:

square area 4.00
circle area 12.57
error: unknown shape: triangle

The loop calls Shape::make twice with the same size and gets two different classes back. The caller calls shape->area() and shape->name() through the base type. The virtual functions, covered in the Polymorphism lesson, choose the right version at run time.

The factory is the only place that names Square and Circle. If a Triangle class is added later, only make changes, and the code that uses the shapes stays the same. The std::make_unique function creates the object and wraps it in a unique_ptr in one step.

An unknown kind throws std::invalid_argument. The factory cannot return a valid shape when the kind is unknown, so it throws, as the Exceptions in class design lesson describes.

Builder: many optional fields

Now take a class with many settings. A server configuration has a required host name and four optional settings: a port, a timeout, a number of retries, and whether to use encrypted connections. Each optional setting has a default value.

One option is a constructor with all five parameters. A caller who only wants to change the port must still pass the other four values, in the right order. Another option is a constructor for every useful combination. Both options become hard to maintain. The builder avoids both problems. The caller names only the settings it wants to change, in any order, and the builder fills in the defaults for the rest.

The builder below is a nested class, which means it is declared inside ServerConfig. The ServerConfig constructor is private, so only the builder can create a configuration. The builder setters return a reference to the builder itself, which lets the calls be chained on one line. The build method checks all the values once, then creates the object.

C++ · editable

Expected output:

localhost:8080 timeout=30s retries=0 tls=no
example.com:443 timeout=30s retries=3 tls=yes
error: port out of range: 70000

The first configuration uses every default. The second sets three of the optional values, and the calls can be written in any order. The reader sees port(443) and tls(true) and knows what each value means, which the positional constructor could not show.

The third configuration uses a port of 70000, which is larger than the highest valid port number, 65535. The build method checks for this and throws before any object is created. The bad variable is never reached, so the program prints only the error.

The builder keeps its own copy of the settings. Calling build a second time gives a new ServerConfig with the same values, because build is a const method and does not change the builder. This makes it possible to make several similar objects from one builder with a small change between them.

Choosing between a factory and a builder

Both patterns keep the caller's code readable, and both keep the object's creation rules in one place. The table shows which one fits each need.

NeedUseWhy
Two or more ways to create the same kind of value, such as from Celsius or from FahrenheitFactory methodEach way has a name, and the class decides how to convert.
One call chooses between several classes, based on inputFactory method that returns a base typeThe caller uses the base type and never names the derived classes.
Many optional settings, some with defaultsBuilderThe caller sets only what it needs, in any order.

A factory is small. It is usually one or two lines per creation method. A builder adds a second class, so use it when the number of settings makes a constructor hard to read. A class with two fields does not need a builder.

Both patterns fit well with the Immutable objects and records lesson earlier in this module. The factory or the builder's build method creates the object once, and the object then has no setters, so its values cannot change after creation.

Common mistakes

Setter returns void, so chaining fails

A builder setter must return the builder for chaining to work. This version returns void:

class Builder {
public:
    void port(int value) { port_ = value; }
    void tls(bool value) { tls_ = value; }
 
private:
    int port_ = 8080;
    bool tls_ = false;
};
 
int main() {
    Builder b;
    b.port(443).tls(true);
}

The compiler reports:

error: invalid use of 'void'
    b.port(443).tls(true);
    ~~~~~~^~~~~

The first call, port(443), returns nothing, so there is nothing to call tls on. The fix is to return Builder& from each setter and return *this at the end, as the builder in the example does.

Constructor overloads that clash

Two constructors that take the same parameter types cannot both exist. The compiler rejects the pair with the message shown in the factory section. This is the first sign that a factory with a clear name is needed.

No validation in build

A builder does not check its values by itself. If build does not check the port, the caller can create a ServerConfig with port 70000, and the error may appear much later, when the program tries to connect. Put every rule in build, so a bad configuration cannot be created. The port and host checks in the example do this.

Returning a raw pointer from a factory

A factory that returns Shape* and creates the object with new leaves the caller with the job of calling delete. A caller who forgets the delete causes a memory leak, which means the program keeps memory it no longer uses. Return a std::unique_ptr instead, as the Shape::make example does, so the object is deleted automatically.

Practice

Exercise 1. Add a third factory to Temperature, named from_kelvin, that takes a value in kelvin and stores it in Celsius. One kelvin unit equals one degree Celsius, and 0 kelvin is -273.15 Celsius. Print the Celsius value of 300 kelvin with two decimal places.

Show the answer

C++ · editable

Expected output: 26.85. The factory subtracts 273.15, so 300 minus 273.15 is 26.85.

Exercise 2. Write a Pizza class with a required size in centimetres and an optional list of toppings. Write a Pizza::Builder with a constructor that takes the size and an add_topping method that adds one topping and returns the builder. The build method must reject a size of zero or less. Build a 30 cm pizza with two toppings and print each topping on its own line.

Show the answer

C++ · editable

Expected output:

30 cm with 2 toppings
- cheese
- olives

The toppings are optional, so the builder starts with an empty list. The size is the one value that the builder cannot do without, so it is passed to the constructor.

Exercise 3. Explain in plain words why the Temperature constructor is private in the factory example.

Show the answer

If the constructor were public, a caller could write Temperature(20.0) and get a value whose unit is not clear. The reader would have to guess whether 20.0 means Celsius, Fahrenheit or kelvin. With a private constructor, every value comes from a named factory, and the name shows the unit. The class also keeps the place where each conversion happens, so a fix to a conversion is made in one function.

Exercise 4. A builder's build method accepts a port of 0 and creates the object. Describe what goes wrong, and write the check that prevents it.

Show the answer

The object is created with a port that no program can connect to. The error does not appear where the object is made. It appears later, when the program tries to use the port, which makes the cause hard to find. The check belongs in build, before the object is created:

if (port_ < 1 || port_ > 65535) {
    throw std::invalid_argument("port out of range: " + std::to_string(port_));
}

Exercise 5. A factory returns Shape* and creates the object with new. Explain why returning std::unique_ptr<Shape> is safer, and what the caller must write in each case.

Show the answer

With Shape*, the caller owns the object and must call delete on it when it is finished. If the caller forgets, or returns early before the delete, the memory is never freed. With std::unique_ptr<Shape>, the pointer owns the object and deletes it when the pointer goes out of scope. The caller does not write delete. The caller still uses -> to call methods, the same as with a raw pointer.

Summary

  • A static factory method gives each way of creating a value its own name, and it is needed when two ways would use the same constructor parameters.
  • Make the constructor private when only the factory should create objects.
  • A factory can return a base type and pick a derived class based on its input, using std::unique_ptr to own the result.
  • A builder collects optional settings one call at a time, and its setters return the builder so calls can be chained.
  • Put every check in the builder's build method, so an invalid object cannot be created.
Practice this topic: coding problems for Object-Oriented Programming →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Exceptions in class design