Previously in this Design Patterns series, we examined the Observer pattern. This time around we’re diving into the Factory Design Pattern—a pattern that is great for cranking out objects when you need them, while keeping your code neat and tidy.
Seriously, this is such a common pattern that you have to know it.
What it is
Think of the Factory Design Pattern as your very own object-making assembly line. Instead of directly instantiating objects with the new
keyword everywhere throughout your code (which can get messy), you delegate this responsibility to a factory class. This approach creates objects in a controlled, centralized way.
The Factory Pattern is part of the creational design patterns family, and it comes in handy when you need to create objects without knowing their exact class type at compile time. It’s like ordering from a menu instead of walking into the kitchen—just tell the factory what you want, and it does the rest.
Why bother
Here’s why the Factory Pattern deserves a spot in your coding toolbox:
- Decoupling: Your code doesn’t need to know the details of object creation. It just asks for an object and gets it. This makes your code cleaner and easier to maintain.
- Flexibility: If you need to add new object types or change the way objects are created, you can update the factory without touching the rest of your code.
- Consistency: The factory ensures objects are created in a standardized way, reducing the risk of errors or inconsistencies.
When to bust it out
The Factory Pattern shines in scenarios like these:
- When Object Creation Logic is Complex: If creating an object involves multiple steps, let the factory handle it.
- When You Work with Multiple Object Types: If your app has multiple classes implementing the same interface or inheriting from the same base class, factories simplify the selection and instantiation process.
- When You Need Scalability: Adding new object types should be easy, and it should not involve editing a dozen files. Factories centralize creation logic, making scaling a breeze.
- When You Want to Follow the Open/Closed Principle: By encapsulating creation logic in a factory, you can add new types without altering existing code—a big win for maintainability.
How to implement the Factory pattern
You can implement the factory pattern in 4 simple steps:
- Define a common interface
- Create concrete implementations
- Create the factory
- Use the factory in your application
If that sounds a little fuzzy to you, stay with me as we look at an example.
Imagine if you will…
Take a look at the component diagram below for an example of the factory design pattern in a simple little program.
Imagine that we want the ability to make any kind of car, but we only want to deal with a CarFactory
. We do not care to dirty our hands building the car directly ourselves, and the CarFactory
is the little bit of magic that will make that possible.
Below is a class diagram (and the PlantUML for it) that illustrates the CarFactory
. CarFactory
implements the interface ICar
, which two different types of cars implement as well, Mustang
and F150
. CarFactory implements the MakeCar
method, which will instantiate any type of car that we want; we just have to pass in the type name. MakeCar("Mustang")
will instantiate a Mustang sports car, while MakeCar("F150")
will instantiate a F150 truck.
Simple enough, right? Here’s a class diagram illustrating this, as well as the PlantUML for generating that diagram.
@startuml skin rose interface ICar { +Build() : void +Drive() : void } class Mustang { +Build() : void +Drive() : void } class F150 { +Build() : void +Drive() : void } class CarFactory { +MakeCar(type: string?) : ICar } ICar <|.. Mustang ICar <|.. F150 CarFactory --> ICar : makes @enduml
Just for the fun of it we created a PlantUML sequence diagram to give you a look at what happens when MakeCar()
is called.
And this activity diagram gives us another view.
The factory pattern in code
Now that you know what a factory pattern does, and you have a handle on our CarFactory
example, let’s see the Factory Pattern in action! Here’s an example in C# that’s all about creating cars. In this example:
- The
CarFactory
encapsulates object creation logic. - The application is decoupled from the
ICar
implementations, making it easy to add new types of cars later. - The code remains clean and follows SOLID principles.
Step 1: Define a Common Interface
public interface ICar { void Build(); void Drive(); }
Step 2: Create Concrete Implementations
public class Mustang : ICar { public void Build() => Console.WriteLine("Built in Flatrock, MI"); public void Drive() => Console.WriteLine("Drove to Florida"); } public class F150 : ICar { public void Build() => Console.WriteLine("Built in Kansas City, MO"); public void Drive() => Console.WriteLine("Drove to Texas"); }
Step 3: Create the Factory
public class CarFactory { public ICar MakeCar(string? type) { if (type == "Mustang") return new Mustang(); if (type == "F150") return new F150(); throw new Exception(); } }
Step 4: Use the Factory in Your Application
class Program { static void Main(string[] args) { CarFactory factory = new CarFactory(); Console.WriteLine("Enter car type to buy:"); string? type = Console.ReadLine(); try { if (type != null) { ICar car = factory.MakeCar(type); car.Build(); car.Drive(); } } catch (Exception) { Console.WriteLine($"Don't know that car..."); } } }
Common Pitfalls & Tips
A few things to keep in mind when you’re considering putting a Factory Pattern into action. Make sure to avoid these mistakes.
- Overusing Factories: Not every object needs a factory. Use it when it adds value, like in complex or scalable systems.
- Too Many Factory Classes: If you find yourself with factories for everything, consider using an Abstract Factory for related groups of objects.
- Documentation: If a factory grows complex, document its purpose and usage to help future developers (and your future self).
The bottom line
The Factory Design Pattern is a fantastic way to centralize and simplify object creation. It’s like having a well-organized kitchen—everything is in its place, and making something new is quick and easy. Whether you’re crafting widgets, vehicles, or something entirely different, the Factory Pattern helps keep your code neat, scalable, and fun to work with.
Ready to put this pattern into practice? Go ahead and build your factory—your codebase will thank you for it! 🚘
For more on the Factory Pattern
Refactoring Guru walks through another example of the factory pattern.
Gang of Four factory method pattern