Practical Design Patterns in C# – Abstract Factory

Intent

The abstract factory design pattern provides an interface to create instances of related objects without directly specifying their concrete classes. This pattern is a superset of the factory method design pattern, as it separates the creation of an object from consumption, while adding the necessary checks to ensure that only related types of objects can be instantiated at a given time.

Problem

In this example, we look at an application that offers options to connect to SQL Server or MySQL database engines to store its data. A rudimentary way of implementing this functionality is to pepper the code with if-else statements throughout the application, which is impractical, brittle and error prone – all traits to avoid in good code.

The source code for this design pattern, and all the others, can be viewed in the Practical Design Patterns repository.

A database operation requires types to make a connection to the database, a command to contain the query and a type to hold a query parameter. All three have to belong to the same family in order to operate correctly. Swapping any type of them for a type from another family is not allowed. E.g. a MySQL connection instance will not be able to operate with a SQL Server command instance. Therefore, if a program has to offer the ability to work with various types of database engines, it has to guarantee that only compatible types are instantiated at a given time.

PlantUML Syntax:<br />
!theme vibrant</p>
<p>interface IDbFactory {<br />
+ CreateDbConnection()<br />
+ CreateDbCommand()<br />
+ CreateDbParameter()<br />
}</p>
<p>interface IDbConnection<br />
interface IDbCommand<br />
interface IDbParameter</p>
<p>IDbFactory -[hidden]left- IDbParameter<br />
IDbConnection -[hidden]right- IDbCommand<br />
IDbCommand -[hidden]right- IDbParameter</p>
<p>class Client #palegreen</p>
<p>Client -up-> IDbFactory : uses</p>
<p>SqlFactory .down.|> IDbFactory</p>
<p>SqlFactory .down.> SqlConnection : creates<br />
SqlFactory .down.> SqlCommand : creates<br />
SqlFactory .down.> SqlParameter : creates</p>
<p>SqlConnection .down.|> IDbConnection<br />
SqlParameter .down.|> IDbParameter<br />
SqlCommand .down.|> IDbCommand</p>
<p>SqlConnection -[hidden]right- SqlCommand<br />
SqlCommand -[hidden]right- SqlParameter</p>
<p>MySqlFactory .up.|> IDbFactory</p>
<p>MySqlFactory .up.> MySqlConnection : creates<br />
MySqlFactory .up.> MySqlCommand : creates<br />
MySqlFactory .up.> MySqlParameter : creates</p>
<p>MySqlConnection .up.|> IDbConnection<br />
MySqlParameter .up.|> IDbParameter<br />
MySqlCommand .up.|> IDbCommand</p>
<p>MySqlConnection -[hidden]right- MySqlCommand<br />
MySqlCommand -[hidden]right- MySqlParameter</p>
<p>

Solution

This pattern consists of at least 2 types – one to define the factory interface, and a concrete factory class that implements this interface. More families of related objects are added to the application by implementing more factories.

The core API is provided by the IDbFactory interface.

public interface IDbFactory
{
    IDbConnection CreateDbConnection();

    IDbCommand CreateDbCommand();

    IDbDataParameter CreateDbDataParameter();
}

IDbFactory defines methods for the database factory.  IDbConnection, IDbCommand and IDbDataParameter are interfaces provided by the framework in the System.Data namespace. All major third-party database vendors provide bindings for their engines which are compatible with these interfaces.

SQL Server Factory

When the application has to operate against SQL Server, it instantiates the SqlFactory class, which implements the IDbFactory interface, and returns the SQL Server-specific concrete types for IDbConnection, IDbCommand and IDbDataParameter.

public class SqlFactory : IDbFactory
{
    public IDbConnection CreateDbConnection()
    {
        return new System.Data.SqlClient.SqlConnection();
    }

    public IDbCommand CreateDbCommand()
    {
        return new System.Data.SqlClient.SqlDbCommand();
    }

    public IDbDataParameter CreateDbDataParmeter()
    {
        return new System.Data.SqlClient.SqlParameter();
    }
}

MySQL Factory

The MySqlFactory class operates in the same way and returns objects from the MySql.Data.MySqlClient namespace.

public class MySqlFactory : IDbFactory
{
    public IDbConnection CreateDbConnection()
    {
        return new MySql.Data.MySqlClient.MySqlConnection();
    }

    public IDbCommand CreateDbCommand()
    {
        return new MySql.Data.MySqlClient.MySqlCommand();
    }

    public IDbDataParameter CreateDbDataParameter()
    {
        return new MySql.Data.MySqlClient.MySqlParameter();
    }
}

Usage

When the application is launched, it determines the database engine it has to operate against (through UI or configuration or some other means), and then instantiates the appropriate IDbFactory implementation. All objects required to operate on the database are subsequently instantiated from this IDbFactory instance.

public class Program
{
    public static void Main(string[] args)
    {
        IDbFactory factory;

        if ("sql" == args[1])
        {
            factory = new SqlFactory();
        }
        else
        {
            factory = new MySqlFactory();
        }

        using (var connection = factory.CreateDbConnection())
        {
            connection.Open();
            var command = factory.CreateDbCommand();
            command.Connection = connection;
            command.CommandText = "SELECT * FROM [Users] WHERE [UserId] = ?";
            var param = factory.CreateDbDataParameter();
            param.DbType = DbType.Int32;
            param.Value = 42;
            command.Parameters.Add(param);

            command.ExecuteReader();
        }
    }
}

Practical Design Patterns in C# – Factory Method

Intent

The factory method is exactly what is says on the label – a method that produces object instances on demand. Ergo, a factory method. The intent of this pattern is to implement a method (in class or instance scope) that separates the creation of object instances from the consumption. It is often used to lazily instantiate objects during dependency injection. The factory method is the foundation to the abstract factory.

Problem

In this example, we look at an application that has to write data to one of two different file formats. The format is selected at runtime through some kind of user input. The application should be able to select the appropriate file encoder, and request it to serialize the data.

Solution

Separate file writer classes are required for each format, but both must share a common interface. The correct class is instantiated, and its Write method is invoked to perform the save operation.

PlantUML Syntax:<br />
!theme vibrant</p>
<p>interface IFileWriterFactory {<br />
+ Create()<br />
}</p>
<p>interface IFileWriter {<br />
+ Write(data:byte[], fileName:string)<br />
}</p>
<p>IFileWriterFactory ..>IFileWriter : creates</p>
<p>JpegWriter .up.|> IFileWriter<br />
PngWriter .up.|> IFileWriter</p>
<p>JpegWriterFactory .down.|> IFileWriterFactory<br />
PngWriterFactory .down.|> IFileWriterFactory</p>
<p>class Client #palegreen</p>
<p>Client -left-> IFileWriterFactory : uses</p>
<p>

The source code for this design pattern, and all the others, can be viewed in the Practical Design Patterns repository.

All writer classes are required to implement the IFileWriter interface.

public interface IFileWriter
{
    void Write(byte[] data, string filename);
}

This interface is implemented by each writer class. The example below shows the JpegWriter and PngWriter classes.

public class JpegWriter : IFileWriter
{
    void Write(byte[] data, string filename) { … }
}
public class PngWriter : IFileWriter
{
    void Write(byte[] data, string filename) { … }
}

Then a factory interface is declared, and a factory class created for each file type.

public interface IFileWriterFactory
{
    public IFileWriter Create();
}
public class JpegWriterFactory : IFileWriterFactory
{
    public Create()
    {
        return new JpegWriter();
    }
}
public class PngWriterFactory : IFileWriterFactory
{
    public Create()
    {
        return new PngWriter();
    }
}

Usage

The application determines the file type that is required, then invokes the Create method on the appropriate writer factory, which in turn returns a concrete implementation of the IFileWriter class.

The application then invokes the Write method on the writer instance, which handles format-specific details such as endianness, file headers, encoding and more.

static class Program
{
    private static _jpegWriterFactory = new JpegWriterFactory();

    private static _pngWriterFactory = new PngWriterFactory();

    static void Main()
    {
        // Image created and serialized into byte[]
        …

        IFileWriter writer;
        if ("jpg" == args[1])
        {
            writer = _jpegWriterFactory.Create();
        }
        else
        {
            writer = _pngWriterFactory.Create();
        }

        writer.Write(data, "lena");
    }
}

Practical Design Patterns in C# – State

The purpose of the state design pattern is to allow an object to alter its behaviour when its internal state changes.

The example of a logging function below is a model candidate for conversion into a state implementation.

The requirements of this function are as follows.

  1. Write the log entry to the end of a text file by default.
  2. The file size is unlimited by default, denoted by setting the value of the maximum file size to 0.
  3. If the value of the maximum file size is greater than 0 then the log file is archived when it reaches this size, and the entry is written to a new empty log file.
  4. If the recycle attribute is enabled and the maximum file size is set to a value greater than 0, then the oldest entry in the log file is erased to make space for the new entry instead of archiving the entire log file.

The implementation of the actual mechanism for writing to disk is not relevant in this example. Rather, the focus is on creating a framework that enables selecting the correct mechanism based on the preferences set.

public void Log(string message)
{
    if (LimitSize)
    {
        if (file.Length < MAX_SIZE)
        {
            // Write to the end of the log file
        }
        else
        {
            if (Recycle)
            {
                // Clear room at the top of the log file
            }
            else
            {
                // Create a new log file with current time stamp
            }

            // Write to the end of the log file
        }
    }
    else
    {
        // Write to the end of the log file
    }
}

The implementation shown above is very naïve, tightly coupled, rigid and brittle. However, it is also a fairly common occurrence in most code bases due to its straightforward approach to addressing the problem.

The complicated logical structure is difficult to decipher, debug and extend. The tasks of determining file size limits, recovering disk space, creating a new file and writing to the file are all discrete from each other and should have no dependency between themselves. But they are all interwoven in this approach and add a huge cognitive overhead in order to understand the execution path.

There are multiple conditions being evaluated before the log entry is made. The programmer has to simulate the computer’s own execution path for each statement before even arriving at the important lines related to writing to disk. A couple of blocks are duplicated code. It would be nice if the execution path could be streamlined and duplicate code removed.

Restructured Code

An eager approach to selecting the preferred mechanism breaks up the conditional statements into manageable chunks. It is logically no different from having the same code in the Log method. But moving it into the mutator makes it easier to understand by adding context into the logic rather than handing it all in a single giant function.

public int MaxFileSize
{
    get
    {
        return _maxFileSize;
    }

    set
    {
        _maxFileSize = value;

        if (0 == MaxFileSize)
        {
            // Activate the unlimited logger
            return;
        }

        if (Recycle)
        {
            // Activate the recycling logger
        }
        else
        {
            // Activate the rotating logger
        }
    }
}

public bool Recycle
{
    get
    {
        return _recycle;
    }

    set
    {
        _recycle = value;

        if (0 == MaxFileSize)
        {
            // Recycling is not applicable when file sizes are unlimited
            return;
        }

        if (Recycle)
        {
            // Activate the recycling logger
        }
        else
        {
            // Activate the rotating logger
        }
    }
}

The comments indicate that you’re still missing some code that actually performs the operation. This is the spot where the behaviour is selected and applied based on the state of the object. We use C# delegates to alter the behaviour of the object at runtime.

public delegate void LogDelegate(Level level, string statement)

public LogDelegate Log
{
    get;
    private set;
}

The Logger class contains private methods that implement the same signature as the Log delegate.

private void AppendAndLog(Level level, string message)
{
}

private void RotateAndLog(Level level, string message)
{
}

private void RecycleAndLog(Level level, string message)
{
}

When the condition evaluation is completed and the logging method has to be selected, a new instance of the Log delegate is created which points to the correct logging method.

if (Recycle)
{
    Log = new LogDelegate(RecycleAndLog);
}
else
{
    Log = new LogDelegate(RotateAndLog);
}

This separates the selection of the correct logging technique to use from the implementation of the technique itself. The logging method implementations can be changed at will to fix bugs or extend their features without adding risk to breaking the rest of the code.