Factory Method Design Pattern in .NET — Real-Time Finance Example

The Factory Method design pattern is a classic solution for a common software engineering challenge: how to create objects without tightly coupling the client code to their concrete types. Instead of instantiating classes directly using new, Factory Method defers object creation to subclasses or dedicated factory methods.

In this blog post, we'll explore the Factory Method pattern in C# with a real-world banking transaction example and also discuss its benefits, drawbacks, and best practices.

 

๐Ÿ’ก What Is the Factory Method Pattern?

The Factory Method pattern provides an interface for creating objects in a superclass but allows subclasses or factory methods to alter the type of objects that will be created.

This promotes:

  • Loose coupling
  • Polymorphism
  • Improved testability and scalability

 

๐ŸŽฏ Purpose

The key goal of Factory Method is to encapsulate object creation logic, so your business logic doesn’t depend on concrete classes. This becomes especially valuable in large systems where extending functionality without modifying existing code is essential.

 

๐Ÿงฑ Key Characteristics

  • Defines a common interface or abstract class for products
  • Delegates instantiation to subclasses or static factory methods
  • Encourages polymorphism and code decoupling

 

✅ Pros

  • Decouples client code from specific object types
  • Aligns with the Open/Closed Principle
  • Improves testability and flexibility
  • Easier to add new functionality without modifying existing code

 

⚠️ Cons

  • Adds extra classes or interfaces, increasing complexity
  • May be overkill for simple or static object creation needs

 

๐Ÿ“ฆ Use Cases

Factory Method is ideal when you need dynamic object creation, such as:

  • Banking transactions (NEFT, IMPS, RTGS)
  • Generating reports in different formats (PDF, Excel, CSV)
  • Creating various types of notifications (Email, SMS, Push)
  • UI components or theme-based widgets

 

๐Ÿ’ผ Real-Time Finance Example: Bank Transaction Processing

Let’s imagine a banking system where you need to process different types of transactions. Rather than hardcoding logic for each transaction type, we can apply the Factory Method pattern for dynamic creation.

 

1️  Define a Common Interface

public interface IBankTransaction

{

    void Process(decimal amount);

}

This interface ensures all transaction types implement a common Process method.

 

2️  Implement Concrete Transaction Types

public class NEFTTransaction : IBankTransaction

{

    public void Process(decimal amount)

    {

        Console.WriteLine($"Processing NEFT transaction of ₹{amount}");

    }

}

 

public class IMPOSTransaction : IBankTransaction

{

    public void Process(decimal amount)

    {

        Console.WriteLine($"Processing IMPS transaction of ₹{amount}");

    }

}

 

public class RTGSTransaction : IBankTransaction

{

    public void Process(decimal amount)

    {

        Console.WriteLine($"Processing RTGS transaction of ₹{amount}");

    }

}

Each transaction class encapsulates its specific logic.

 

๐Ÿงผ Optional Improvement: Use Enum Instead of String

Avoiding raw strings can reduce runtime errors:

public enum TransactionType

{

    NEFT,

    IMPS,

    RTGS

}

 

3️  Create the Factory

public static class TransactionFactory

{

    public static IBankTransaction CreateTransaction(TransactionType type)

    {

        return type switch

        {

            TransactionType.NEFT => new NEFTTransaction(),

            TransactionType.IMPS => new IMPOSTransaction(),

            TransactionType.RTGS => new RTGSTransaction(),

            _ => throw new ArgumentException("Invalid transaction type")

        };

    }

}

This factory method encapsulates the creation logic, making the client code clean and decoupled.

 

4️  Client Code Usage

class Program

{

    static void Main()

    {

        // Imagine this comes from user input or a config

        TransactionType transactionType = TransactionType.IMPS;

        decimal amount = 5000;

 

        IBankTransaction transaction = TransactionFactory.CreateTransaction(transactionType);

        transaction.Process(amount);

    }

}

No direct reference to concrete types — just interaction via the IBankTransaction interface.

 

๐Ÿ” Advanced Concepts (Optional Enhancements)


✅ Dependency Injection (DI) Integration

You can also register each implementation in an IoC container like Microsoft.Extensions.DependencyInjection and use DI to resolve them dynamically using factories or keyed services.


✅ Unit Testing Benefits

By depending on IBankTransaction, client code becomes highly testable:

[Test]

public void Test_Transaction_Processing()

{

    var mockTransaction = new Mock<IBankTransaction>();

    mockTransaction.Setup(x => x.Process(It.IsAny<decimal>()));

 

    var transactionService = new TransactionService(mockTransaction.Object);

    transactionService.Execute(5000);

 

    mockTransaction.Verify(x => x.Process(5000), Times.Once);

}

 

✅ Benefits Recap in This Scenario

  • Extensible: New transaction types (e.g., UPI) require only a new class and factory entry
  • Testable: Easy to mock and test business logic
  • Clean client code: No need to know about concrete classes
  • Follows SOLID: Especially Open/Closed and Single Responsibility principles

 

๐Ÿงพ Summary

Aspect

Benefit

Decoupling

Client code knows only interfaces

Extensibility

Add new types with minimal changes

Testability

Mocks can be easily injected

Maintainability

Factory centralizes object creation

The Factory Method pattern is a robust and scalable design choice for applications where object creation logic varies based on runtime input, configurations, or environment — such as finance platforms that support multiple transaction types or reporting tools.

 

๐Ÿ—จ️ Final Thoughts

Factory Method isn’t always necessary — don’t apply it for trivial cases. But in the right context, it promotes flexibility, testability, and maintainability, especially in large systems with evolving requirements.

 

Comments

Popular posts from this blog

Logging in .NET Core: Built-in Logging vs Serilog with Full Implementation Guide

Implementing Single Sign-On (SSO) in .NET Core and Angular