Factory Method Pattern in C#: With Abstract Classes vs. Interfaces

Two Approaches for same — Implementing the Factory Method Pattern

DotNet Full Stack Dev
4 min readDec 3, 2024

The Factory Method Pattern is a creational design pattern that provides a way to instantiate objects without exposing the creation logic to the client. Instead, the client relies on a common interface or base class to interact with the objects. This approach decouples object creation from its usage, making your code more flexible and maintainable.

In this blog, we’ll explore two ways to implement the Factory Method Pattern in C#: using abstract classes and using interfaces. We’ll walk through examples, explain the differences, and provide guidance on when to choose each approach.

📌Explore more at: https://dotnet-fullstack-dev.blogspot.com/
🌟 Clapping would be appreciated! 🚀

What is the Factory Method Pattern?

The Factory Method Pattern defines an interface or abstract class for creating an object, but lets subclasses decide which class to instantiate. It’s commonly used when:

  • A class needs to delegate the responsibility of instantiating its dependencies.
  • You want to decouple object creation from its usage.
  • You need to support Open/Closed Principle — the system should be open to extension but closed to modification.

Scenario: A Vehicle Factory

To illustrate the pattern, let’s imagine a vehicle manufacturing system. The system needs to produce different types of vehicles, such as Cars and Trucks, depending on the client’s needs. Each type of vehicle has its unique behavior but shares some common properties, such as being a vehicle.

We’ll implement the Factory Method Pattern using two approaches:

  1. Using Abstract Classes.
  2. Using Interfaces.

Approach 1: Using Abstract Classes

In this approach, we use an abstract class to define common properties and methods for the factory and the product.

Step 1: Define the Abstract Product

Create an abstract base class for vehicles.

public abstract class Vehicle
{
public abstract string GetVehicleType();
}

This class serves as the blueprint for all vehicles.

Step 2: Implement Concrete Products

Create specific vehicle types by inheriting from the base class.

public class Car : Vehicle
{
public override string GetVehicleType()
{
return "Car";
}
}

public class Truck : Vehicle
{
public override string GetVehicleType()
{
return "Truck";
}
}

Each vehicle provides its own implementation of the GetVehicleType method.

Step 3: Create an Abstract Factory

Define an abstract class for the factory.

public abstract class VehicleFactory
{
public abstract Vehicle CreateVehicle();
}

This factory defines the method for creating vehicles but doesn’t specify which type of vehicle will be created.

Step 4: Implement Concrete Factories

Implement specific factories to create different types of vehicles.

public class CarFactory : VehicleFactory
{
public override Vehicle CreateVehicle()
{
return new Car();
}
}

public class TruckFactory : VehicleFactory
{
public override Vehicle CreateVehicle()
{
return new Truck();
}
}

Step 5: Client Code

The client uses the factory to create vehicles without worrying about the underlying logic.

public class Program
{
public static void Main()
{
VehicleFactory carFactory = new CarFactory();
Vehicle car = carFactory.CreateVehicle();
Console.WriteLine($"Vehicle created: {car.GetVehicleType()}");

VehicleFactory truckFactory = new TruckFactory();
Vehicle truck = truckFactory.CreateVehicle();
Console.WriteLine($"Vehicle created: {truck.GetVehicleType()}");
}
}

Output

Vehicle created: Car
Vehicle created: Truck

Approach 2: Using Interfaces

In this approach, we use interfaces to define contracts for the products and factories.

Step 1: Define the Product Interface

Create an interface for vehicles.

public interface IVehicle
{
string GetVehicleType();
}

This interface defines the contract that all vehicle types must follow.

Step 2: Implement Concrete Products

Create specific vehicle types by implementing the interface.

public class Car : IVehicle
{
public string GetVehicleType()
{
return "Car";
}
}

public class Truck : IVehicle
{
public string GetVehicleType()
{
return "Truck";
}
}

Each vehicle provides its own implementation of the GetVehicleType method.

Step 3: Create a Factory Interface

Define an interface for the factory.

public interface IVehicleFactory
{
IVehicle CreateVehicle();
}

This factory interface defines the method for creating vehicles.

Step 4: Implement Concrete Factories

Implement specific factories to create different types of vehicles.

public class CarFactory : IVehicleFactory
{
public IVehicle CreateVehicle()
{
return new Car();
}
}

public class TruckFactory : IVehicleFactory
{
public IVehicle CreateVehicle()
{
return new Truck();
}
}

Each factory knows how to create its specific type of vehicle.

Step 5: Client Code

The client uses the factory to create vehicles without worrying about the underlying logic.

public class Program
{
public static void Main()
{
IVehicleFactory carFactory = new CarFactory();
IVehicle car = carFactory.CreateVehicle();
Console.WriteLine($"Vehicle created: {car.GetVehicleType()}");

IVehicleFactory truckFactory = new TruckFactory();
IVehicle truck = truckFactory.CreateVehicle();
Console.WriteLine($"Vehicle created: {truck.GetVehicleType()}");
}
}

Output

Vehicle created: Car
Vehicle created: Truck

When to Choose Which?

Use Abstract Classes When:

  • You want to share common functionality or behavior between derived classes.
  • You need to define base methods or properties with default implementations.

Use Interfaces When:

  • You need to enforce a contract across unrelated classes.
  • You want to implement multiple types of functionality in a single class (since C# supports multiple interface inheritance).

Conclusion

The Factory Method Pattern is a versatile and powerful tool for creating objects in a flexible and maintainable way. Whether you use abstract classes or interfaces depends on your specific requirements:

  • Use abstract classes for tightly related components with shared behavior.
  • Use interfaces for loosely coupled components focused on contracts.

By mastering both approaches, you can design software that adheres to SOLID principles and is easier to extend and maintain.

--

--

DotNet Full Stack Dev
DotNet Full Stack Dev

Written by DotNet Full Stack Dev

Join me to master .NET Full Stack Development & boost your skills by 1% daily with insights, examples, and techniques! https://dotnet-fullstack-dev.blogspot.com

Responses (1)