How to use Factory Method Pattern in C#

By FoxLearn 12/27/2024 9:53:32 AM   537
The Factory Method is a creational design pattern that defines an interface for creating objects in a superclass.

However, it allows subclasses to decide which specific class of objects to instantiate. This pattern helps in delegating the responsibility of object creation to subclasses, providing flexibility in object creation while maintaining a consistent interface.

Structure

factory method pattern in c#

 

Classes and objects that participate in this pattern include:

- Product: defines the interface of the object that the factory method creates.

- ConcreteProduct: implements Product interface classes.

- Creator: Defining a factory method, returning one object is a specific type of product. Creator can be the default factory method that returns the default ConcreteProduct object, it can call the factory method to create the Product object.Defining a factory method

- ConcreteCreator override factory method to return the ConcreteProduct instance.

How to Implement Factory Method Pattern in C#?

class BuilderPattern
{
    static void Main()
    {
        Creator[] creators = new Creator[2];
        creators[0] = new ConcreteCreatorA();
        creators[1] = new ConcreteCreatorB();
        foreach (Creator creator in creators)
        {
            Product product = creator.FactoryMethod();
            Console.WriteLine("Created {0}",
              product.GetType().Name);
        }
    }
}

abstract class Product
{
}

class ConcreteProductA : Product
{
}

class ConcreteProductB : Product
{
}

abstract class Creator
{
    public abstract Product FactoryMethod();
}

class ConcreteCreatorA : Creator
{
    public override Product FactoryMethod()
    {
        return new ConcreteProductA();
    }
}

class ConcreteCreatorB : Creator
{
    public override Product FactoryMethod()
    {
        return new ConcreteProductB();
    }
}

Result

Created ConcreteProductA
Created ConcreteProductB

This example demonstrates how you can use a factory method to customize the creation of UI components (like buttons) for different operating systems, all while maintaining a consistent overall design. The Window class defines the structure, while subclasses like MacWindow or LinuxWindow specify the details of the UI components, such as the button style.

// The creator class declares the factory method that must
// return a product object. Subclasses typically provide
// the implementation of this method.
public abstract class Window
{
    // The creator might also offer a default implementation
    // of the factory method.
    public abstract Button CreateButton();

    // Despite the name, the creator's main responsibility
    // isn't to create products, but to handle core business
    // logic that relies on product objects provided by the
    // factory method. Subclasses can change the business
    // logic by overriding the factory method to return
    // a different product type.
    public void Render()
    {
        // Use the factory method to create a product.
        Button actionButton = CreateButton();
        // Utilize the created product.
        actionButton.OnClick(CloseWindow);
        actionButton.Render();
    }

    private void CloseWindow()
    {
        Console.WriteLine("Window is closing...");
    }
}

// Concrete creators override the factory method to produce
// a different type of product.
public class MacWindow : Window
{
    public override Button CreateButton()
    {
        return new MacButton();
    }
}

public class LinuxWindow : Window
{
    public override Button CreateButton()
    {
        return new LinuxButton();
    }
}

// The product interface defines the operations that all
// concrete products must implement.
public interface Button
{
    void Render();
    void OnClick(Action callback);
}

// Concrete products provide their own implementations of
// the product interface.
public class MacButton : Button
{
    public void Render()
    {
        Console.WriteLine("Rendering macOS-style button.");
    }

    public void OnClick(Action callback)
    {
        Console.WriteLine("Binding macOS-specific click event.");
        callback?.Invoke();
    }
}

public class LinuxButton : Button
{
    public void Render()
    {
        Console.WriteLine("Rendering Linux-style button.");
    }

    public void OnClick(Action callback)
    {
        Console.WriteLine("Binding Linux-specific click event.");
        callback?.Invoke();
    }
}

public class Application
{
    private Window window;

    // The application selects the appropriate creator type
    // based on the environment configuration.
    public void Initialize()
    {
        var config = ReadApplicationConfigFile();

        if (config.OS == "macOS")
        {
            window = new MacWindow();
        }
        else if (config.OS == "Linux")
        {
            window = new LinuxWindow();
        }
        else
        {
            throw new Exception("Error! Unknown operating system.");
        }
    }

    // The client code interacts with a concrete creator
    // through its base interface. As long as the client uses
    // the base interface, it can work with any subclass of creator.
    public void Main()
    {
        Initialize();
        window.Render();
    }

    private dynamic ReadApplicationConfigFile()
    {
        // Here, we'd normally read from a configuration file.
        // For this example, we'll just return a mock configuration.
        return new { OS = "macOS" }; // Change this to "Linux" to test the Linux version.
    }
}

// Usage
public class Program
{
    public static void Main()
    {
        var app = new Application();
        app.Main();  // This will create and render a macOS-style button.
    }
}

Applicability

The Factory Method is useful when you don't know the exact types and dependencies of objects your code should interact with in advance. It separates the construction of objects from their use, making it easier to extend object creation logic independently of the rest of the code.

To add a new product type, you simply create a new subclass of the creator class and override its factory method. This approach is ideal when you want to provide a way for users to extend a library or framework's internal components. For example, if an open-source UI framework provides square buttons but you need round buttons, you can create a subclass that overrides the factory method to return RoundButton objects instead of the default square ones.

Additionally, the Factory Method is useful when you want to conserve system resources by reusing existing objects rather than creating new ones repeatedly. For resource-heavy objects (like database connections or network resources), you can implement object pooling through a factory method. This allows the system to return an existing object from a pool when needed, reducing resource consumption and avoiding unnecessary duplication of code.