Skip to main content

Factory Design Pattern

The Factory pattern is used to provide a common interface to construct objects that have a common base class. The main benefit is that you can dynamically choose different policies at runtime, instead of forcing a specific policy at compile-time.

Let's say we want to make a factory for I/O schedulers. An I/O scheduler is a policy to decide the order with which I/O requests are sent to a storage device.

The Base Class

First, we define the base class I/O scheduler.

class IoSched {
public:
virtual void PushRequest() = 0;
virtual void PopRequest() = 0;
};

We have two methods: push an I/O request and pop an I/O request.

The Derived Classes

Now we implement some specific I/O scheduling policies. In this example, we provide RoundRobinSched and DeadlineSched.

class RoundRobinSched : public IoSched {
public:
void PushRequest() override {}
void PopRequest() override {}
};

class DeadlineSched : public IoSched {
public:
DeadlineSched(int a, int b) {}
void PushRequest() override {}
void PopRequest() override {}
};

DeadlineSched takes parameters a & b. RoundRobinSched has no parameters.

The Factory

enum class IoSchedType {
kRoundRobin,
kDeadline
};

class IoSchedFactory {
public:
static std::unique_ptr<IoSched> Get(IoSchedType type) {
switch (type) {
case IoSchedType::kRoundRobin:
return std::make_unique<RoundRobinSched>();
case IoSchedType::kDeadline:
return std::make_unique<DeadlineSched>(0, 1);
}
}
};

Usage

Now you can use the factory to construct your object.

int main() {
auto sched = IoSchedFactory::Get(IoSchedType::kRoundRobin);
sched->PushRequest();
sched->PopRequest();
}

Discussion

One of the main benefits of the factory design is that you can change policies dynamically. I/O schedulers in the Linux kernel for example can be changed at any time. Below is an example of using the Factory to dynamically set the I/O scheduling policy.

int main(int argc, char **argv) {
IoSchedType sched_type = static_cast<IoSchedType>(std::stoi(argv[1]));
auto sched = IoSchedFactory::Get(sched_type);
sched->PushRequest();
sched->PopRequest();
}

Factories can have a performance penalty since they require a memory allocation to use. However, this penalty is almost always insignificant. If a factory matches your use case, then make your code readable first. If it turns out to cause significant performance problems, then fix it later. You can make a custom memory allocator to fix the issue if it's really needed.