Summary: in this tutorial, you’ll learn about the C# factory method design pattern and how to use it to create objects without tightly coupling the object creation code to the client code.
Introduction to the C# factory method design pattern
A real-world factory produces products. In programming, a factory creates objects. When a method creates and returns an object, it is called a factory method.
The Factory Method pattern is a creational design pattern, which provides an interface for creating objects in a superclass but allows subclasses to decide the object type.
The following UML diagram illustrates the Factory Method pattern:
The Factory Method pattern consists of the following participants:
Creator
: the abstract class that defines a factory method for creating objects. Thecreator
can be an interface if it doesn’t have a shared implementation with the subclasses.Product
: the abstract class that defines the interface for the objects created by the factory method. Like theCreator
, theProduct
can be an interface.ConcreteFactory
: the concrete class that inherits from theCreator
class. TheConcreteFactory
class createsConcreateProduct
that inherits from theProduct
.ConcreteProduct
: the concrete class that extends theProduct
class.
Here’s the implementation of the factory method pattern in C#:
namespace FactoryMethod;
public abstract class Product {}
public abstract class Creator
{
public abstract Product FactoryMethod();
public void Operation()
{
var product = FactoryMethod();
// process the product
// ...
Console.WriteLine($"Work with the {product}");
}
}
public class ConcreateProduct: Product {}
public class ConcreteFactory : Creator
{
public override Product FactoryMethod() => new ConcreateProduct();
}
public class Program
{
public static void Main(string[] args)
{
var creator = new ConcreteCreator();
creator.Operation();
}
}
Code language: C# (cs)
Output:
Work with the FactoryMethod.ConcreateProduct
Code language: JavaScript (javascript)
Factory method pattern vs. the new keyword
1) The new keyword creates dependencies between the client code and the concrete implementations of classes
When you use the new
keyword to create objects of classes, you create dependencies between the client code and the concrete implementation of the classes.
If the classes change, you must change the client code to accommodate the new implementation. This makes your code tightly coupled and difficult to extend.
The Factory Method pattern decouples the client code from the implementation of the objects being created.
The client code only needs to know the factory interface, which provides a way to create objects without knowing the specific implementation of the objects it creates.
Therefore, the factory method makes your code more flexible, testable, and easier to extend.
2) The new keyword makes it difficult to swap out implementations
The new
keyword also makes it difficult to swap out implementations. If you introduced a new implementation or replace an existing one, you need to update the client code. This violates the open-closed principle.
On the other hand, using the Factory Method makes it easier to swap out implementations without having to modify the client code.
The reason is that the client code only needs to know the factory interface and can use it to create objects without knowing the specific implementation being used.
C# Factory Method design pattern example
The following program demonstrates how to use the Factory Method pattern to implement a discount policy for a simplified order system:
namespace FactoryMethod;
public abstract class Discount
{
public abstract decimal GetPercentage();
}
public class RegularDiscount : Discount
{
public override decimal GetPercentage() => 0.1m;
}
public class IrregularDiscount : Discount
{
public override decimal GetPercentage() => 0.15m;
}
public abstract class DiscountPolicy
{
public abstract Discount Create();
public decimal Apply(decimal Price)
{
var discount = Create();
return Price * (1 - discount.GetPercentage());
}
}
public class RegularDiscountPolicy : DiscountPolicy
{
public override Discount Create() => new RegularDiscount();
}
public class IrregularDiscountPolicy : DiscountPolicy
{
public override Discount Create() => new IrregularDiscount();
}
public class Order
{
private readonly decimal _netAmount;
public decimal Amount => OrderDiscountPolicy.Apply(_netAmount);
public DiscountPolicy OrderDiscountPolicy
{
get; private set;
}
public Order(decimal amount, DiscountPolicy discountPolicy)
{
_netAmount = amount;
OrderDiscountPolicy = discountPolicy;
}
}
public class Program
{
public static void Main()
{
var order = new Order(1000, new IrregularDiscountPolicy());
Console.WriteLine(order.Amount);
}
}
Code language: C# (cs)
Output:
850.00
Code language: CSS (css)
How it works.
The following UML diagram illustrates how the relationships between classes in the program:
First, define the DiscountPolicy as an abstract class. The DiscountPolicy class has the Create() method that creates and returns a new Discount object.
Next, define the RegularDiscountPolicy
and IrregularDiscountPolicy
classes that extend the DiscountPolicy
class. The Create()
method of these classes returns a RegularDiscount
and IrregularDiscount
object, respectively.
Then, define the Discount
class as an abstract class. The Discount
class has two concrete classes including RegularDiscount
and IrregularDiscount
classes.
After that, define the Order
class that uses the DiscountPolicy
class. The Order
class stores a net amount of the order and a discount policy. The Amount
property returns the amount after applying the discount policy to the net amount.
Finally, create an Order
object with a net amount of 1000
and an IrregularDiscountPolicy
object in the Main()
method of the Program
class and displays the amount after applying the discount policy to the console.
In this example, you can swap the discount policy from the IrregularDiscountPolicy
to RegularDiscountPolicy
without modifying the Order
class.
Also, you can introduce a new discount policy e.g., SpecialDiscountPolicy
and swap it with the current discount policy without changing the Order
class.
Summary
- Use the Factory Method design pattern to create objects without tightly coupling the object creation code to the client code.