An Intro to Abstract Factory C# in Under 10 Minutes


Sometimes, it seems like we are writing the same code.

If statements.

Create a new method.

Use async and await.

But no matter what code statement you use, the ability to create new objects is the core of every software application. So naturally, the need to create new instances efficiently kick-started various creational design patterns, such as the Prototype and Singleton. But you can also use the Abstract Factory to create a family of products at once.

Abstract Factory is a design pattern used to provide an interface for creating a group of factories with a common use case without specifying the concrete classes. Each concrete implementation of the abstract factory creates concrete objects that are part of the same family of products.

Abstract Factory, like any design pattern, defines a template or skeleton code that helps you structure your design and organize your code. This article describes a simple yet handy Abstract Factory design pattern in C#.

What is the Abstract Factory design pattern?

Abstract Factory pattern in a nutshell

Abstract Factory is a popular creational design pattern you can use when you need to define a relationship of interconnected objects but don’t want to specify their concrete classes. Generally, the Abstract Factory Design Pattern defines a new interface to help you create a family of products without needing to create the actual product.

As an illustration, imagine that you are creating an interactive application with various display modes. For example, you can have dark, light, and dim modes. Naturally, each display mode should have distinct components for its design, which means the dark version will have unique buttons, icons, and state changes. Now, you may hard-code the widgets for each mode. But that will result in an application that may become challenging to change later.

With this in mind, you can use the Abstract Factory Design Pattern instead.

In this case, you can create an abstract ComponentFactory class to define an interface for creating each component. This implementation will also include abstract classes for each component type and a concrete subclass that will aid in implementing that component for your different display modes.

Here, clients can call ComponentFactory methods that will return a new component object for each abstract class. For this reason, clients can get component instances without being aware of their concrete classes, making it easy for our application to remain independent of the display mode.

Creational design patterns

Creational design patterns are those that deal with the creation of objects. This can include specifying how objects are created and what processes are used to create them. You can use these patterns to create objects in various ways, such as creating an object in a step-by-step manner or creating a whole family of objects.

There are five main types of creational patterns:

  1. Abstract Factory – A factory that produces a generic interface for creating families of related objects.
  2. Builder – A pattern for creating complex objects by separating the construction process from the final product.
  3. Factory Method – A pattern for creating objects by delegating the object creation task to a subclass.
  4. Prototype – A prototype is a class that can create copies of itself.
  5. Singleton – A pattern for ensuring a class has only one instance, and that instance is accessible from anywhere in the program.

When to use the Abstract Factory design pattern?

 You can apply the Abstract Factory design pattern to many different kinds of objects. So, it’s essential to understand when it’s appropriate to use it. For example, if you have a group of objects with the same purpose, you could use an Abstract Factory to define an interface for creating new objects.

In general, use the Abstract Factory design pattern when:

  • An application should be independent of how its product classes are created and composed.
  • An application has multiple families of products.
  • You have a group of classes that are designed to be used together.
  • You want to provide a group of classes, but you want to reveal only the interface and not the actual implementations.

In the previous example, we’ve used the Abstract Factory pattern to create components that are not dependent on their display mode. This makes it easy for our application to remain independent of the display mode and allows us to update our component definitions without changing any existing code.

Abstract Factory vs. abstract class

The Abstract Factory design pattern is often confused with the more general concept of abstract classes.

There are several differences between an Abstract Factory and an abstract class. An Abstract Factory is a design pattern that allows for the creation of objects without specifying their concrete class. An abstract class is a class that contains one or more abstract methods. Abstract methods are methods that a concrete subclass must implement.

You can use an abstract factory to create objects that conform to a specific interface. In contrast, you can use the abstract class to provide a general implementation of an algorithm that subclasses can extend.

How to implement the Abstract Factory in C# – Structural code

To illustrate how the Abstract Factory Design Pattern works in C#, we can explore some structural code. The core participants are:

  1. AbstractFactory – This object declares an interface for any operations that create abstract products.
  2. ConcreteFactory – You use this concrete class to create concrete product objects.
  3. AbstractProduct – Defines an interface for a product type.
  4. ConcreteProduct: – Implements the AbstractProduct interface and defines a product object.
  5. Client – This class will use our AbstractFactory and AbstractProduct interfaces to interact with our application.

The following class diagram represents the interaction between classes.

The following code represents the basic structural code of the Abstract Factory in C#:

namespace AbstractFactoryPattern;

/// Create our Abstract Factory:
abstract class AbstractFactory
{
    public abstract AbstractProductI CreateProductI();

    public abstract AbstractProductII CreateProductII();
}

class ConcreteFactoryI : AbstractFactory
{
    public override AbstractProductI CreateProductI()
    {
        return new FirstProductOfAbstractI();
    }

    public override AbstractProductII CreateProductII()
    {
        return new FirstProductOfAbstractII();
    }
}

class ConcreteFactoryII : AbstractFactory
{
    public override AbstractProductI CreateProductI()
    {
        return new SecondProductOfAbstractI();
    }

    public override AbstractProductII CreateProductII()
    {
        return new SecondProductOfAbstractII();
    }
}

/// Our First Abstract Product
abstract class AbstractProductI
{
}

/// Our Second Abstract Product
abstract class AbstractProductII
{
    public abstract void Interact(AbstractProductI i);
}

class FirstProductOfAbstractI : AbstractProductI
{
}

class FirstProductOfAbstractII : AbstractProductII
{
    public override void Interact(AbstractProductI i)
    {
        Console.WriteLine(this.GetType().Name +
        " is linking with " + i.GetType().Name);
    }
}

class SecondProductOfAbstractI : AbstractProductI
{
}

class SecondProductOfAbstractII : AbstractProductII
{
    public override void Interact(AbstractProductI i)
    {
        Console.WriteLine(this.GetType().Name +
        " is linking with " + i.GetType().Name);
    }
}

class Client
{
    private AbstractProductI _abstractProductI;
    private AbstractProductII _abstractProductII;

    public Client(AbstractFactory factory)
    {
        _abstractProductI = factory.CreateProductI();
        _abstractProductII = factory.CreateProductII();
    }

    public void Run()
    {
        _abstractProductII.Interact(_abstractProductI);
    }
}

Let’s connect everything together now:

using AbstractFactoryPattern;

AbstractFactory factoryI = new ConcreteFactoryI();
Client clientI = new Client(factoryI);
clientI.Run();

AbstractFactory factoryII = new ConcreteFactoryII();
Client clientII = new Client(factoryII);
clientII.Run();

Code Output:

FirstProductOfAbstractII is linking with FirstProductOfAbstractI
SecondProductOfAbstractII is linking with SecondProductOfAbstractI

Even though the sample code in the previous example uses abstract classes to define the Abstract Factory base class, you can also use the interface instead.

Abstract Factory pattern – Real-world example in C#

Let’s apply our knowledge of the Abstract Factory design pattern to a real-world example. In this case, we will explore a teacher management application for math teachers in two fictional schools, the first in London and the other in Washington, DC.

namespace AbstractFactoryPattern;

abstract class TeacherManagementFactory
{
    public abstract MathTeacher CreateMathTeacher();
    public abstract AdvancedMathTeacher CreateAdvancedMathTeacher();
}

/// real world implementation of our earlier "ConcreteFactoryI" class:
class WashingtonHighFactory : TeacherManagementFactory
{
    public override MathTeacher CreateMathTeacher()
    {
        return new BasicMath();
    }

    public override AdvancedMathTeacher CreateAdvancedMathTeacher()
    {
        return new APMath();
    }
}

/// real world impelmentation of our earlier "ConcreateFactoryII" class:
class LondonSecondarySchoolFactory : TeacherManagementFactory
{
    public override MathTeacher CreateMathTeacher()
    {
        return new GCSEMath();
    }

    public override AdvancedMathTeacher CreateAdvancedMathTeacher()
    {
        return new AdditionalMath();
    }
}

/// real world implementation of our earlier "AbstractProductI" asbtract class:
abstract class MathTeacher
{
}

/// real world implementation of our earlier "AbstractProductII" class:

abstract class AdvancedMathTeacher
{
    public abstract void CanTeach(MathTeacher m);
}

class BasicMath : MathTeacher
{
}

class APMath : AdvancedMathTeacher
{
    public override void CanTeach(MathTeacher m)
    {
        // AP Math Teacher can also teach Basic Math
        Console.WriteLine("A USA " + 
            this.GetType().Name + " teacher can teach " + 
            m.GetType().Name);
    }
}

class GCSEMath : MathTeacher
{
}

class AdditionalMath : AdvancedMathTeacher
{
    public override void CanTeach(MathTeacher m)
    {
        // Additional Math Teacher can also teach GCSE Math
        Console.WriteLine("A UK " + 
            this.GetType().Name + " teacher can teach " + 
            m.GetType().Name);
    }
}

/// Real World Implementation of our Client Class
class PondSchools
{
    private MathTeacher _mathTeacher;
    private AdvancedMathTeacher _advancedMathTeacher;

    public PondSchools(TeacherManagementFactory factory)
    {
        _mathTeacher = factory.CreateMathTeacher();
        _advancedMathTeacher = factory.CreateAdvancedMathTeacher();
    }

    public void RunTeacherStatus()
    {
        _advancedMathTeacher.CanTeach(_mathTeacher);
    }
}

And the usage:

TeacherManagementFactory unitedStates = 
    new WashingtonHighFactory();
PondSchools ourSchools = new PondSchools(unitedStates);
ourSchools.RunTeacherStatus();

// create and run the teacher management factory for London, UK:
TeacherManagementFactory unitedKingdom = 
    new LondonSecondarySchoolFactory();
ourSchools = new PondSchools(unitedKingdom);
ourSchools.RunTeacherStatus();

Code output:

A USA APMath teacher can teach BasicMath
A UK AdditionalMath teacher can teach GCSEMath

What are the advantages of using the Abstract Factory design pattern?

The advantages of the Abstract Factory design pattern are:

  • concrete classes are isolated
  • exchanging product families is easy
  • there is consistency between products

The Abstract Factory allows you to isolate the concrete classes from the rest of the application. The rest of the code manipulates with instances through the abstract interface. Product classes don’t appear anywhere else in the code.

Also, you can easily exchange product families. That’s because the concrete factory class appears only once in the application when it’s instantiated. Because of this, it’s easy to change the concrete factory on the fly. The Abstract Factory creates a complete family of products, and you can change the whole family at once.

Finally, the Abstract Factory forces the implementation that the application uses the objects from only one family at a time. This provides consistency in the code.

What are the disadvantages of using the Abstract Factory design pattern?

The disadvantages of the Abstract Factory design pattern are:

  • adding new kinds of products can be difficult
  • it makes the code more complicated

It’s not straightforward to extend abstract factories to include a new kind of product. The reason is that the Abstract Factory interface initially sets the set of products that need to be created. Adding a new product to the mix involves changing all implementations of the Abstract Factory.

The Abstract Factory pattern can complicate the code since you need to create a lot of classes and interfaces to implement this pattern.

What are related patterns to the Abstract Factory design pattern?

The Factory Method pattern and the Singleton pattern are related to the Abstract Factory design pattern. Abstract Factory can be implemented using factory methods, and it is often a singleton.

What is the difference between Factory Method and Abstract Factory?

The Factory Method is a creational design pattern that allows an object to be created without specifying the exact class you will create. The Abstract Factory design pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

Conclusion

In conclusion, the Abstract Factory is a very useful design pattern to implement when working with a family of objects that a factory creates.

If you need to abstract away the details of object creation, or if you need to be able to change the types of objects that are created at runtime, this is an ideal pattern to use. With some thought and planning, the Abstract Factory design pattern can be a valuable addition to your toolkit.

Recent Posts