Design patterns in real world

Introduction

Recently I was asked whether I use design patterns in my work, if any, what are those.

Well, certainly I do. But at that moment I found it hard to quickly pull some real world examples. So I’m writting this post.

Hopefully it could help reminding me some of them if I’m asked again.

Decorator Pattern

The first example came into my mind was the System.IO.Stream classes. You can wrap a stream with another in the constructor.

For example, to decompress a file at path, you create a stream as follows:

var decompressor = new GZipStream(new FileStream(path, FileMode.Open), CompressionMode.Decompress);

And, if you’re sending its AES encrypted contents over the network, you create another stream like:

var encryptedSender = new CryptoStream(new NetworkStream(socket), aes.CreateEncryptor());

Finally, to perform the sending:

await decompressor.CopyToAsync(encryptedSender);

Any new functionality is dynamically attached via composition in the Decorator pattern, no matter how complex the composition ends up, they are used as if their base classes.

Factory Pattern

There are many examples for this in the .NET Framework. Such as the Parse methods under Int32, Double, and etc.

A good old example is the Create method on the System.Net.WebRequest class. Sub classes such as HttpWebRequest or FtpWebRequest will be created according to the uri given to the method.

var r1 = WebRequest.Create("https://www.abc.com");  // HttpWebRequest
var r2 = WebRequest.Create("ftp://ftp.abc.com");    // FtpWebRequest

Another example is the Expression class under the System.Linq.Expression namespace. Essentially you use the static methods under the Expression class to create tree nodes of various types to model expression trees. However, most of the time the C# compiler will be creating those method calls for you.

In my own work of designing the API to create Blocks for Slack integration, I used the Factory pattern. Essentially I use the IBlock interface to represent blocks, and created a Blocks class that contains various static methods to create appropiate concrete block classes.

slack.Chat.SendMessage("#random", new List<IBlock>
{
    Blocks.Header("Header"),
    Blocks.Divider(),
    Blocks.Section(
        Blocks.MarkdownText("*Fields*"),
        Blocks.Field(Blocks.MarkdownText("*Field 1*: 123")),
        Blocks.Field(Blocks.MarkdownText("*Field 2*: xyz"))
    )
});

Factories hide the complexities of object creation.

Visitor Pattern

Speaking of expression trees, a common pattern to work with tree-like structures are known as the Visitor Pattern.

If you look at the methods of Expression, you’ll find an internal virtual Accept(ExpressionVisitor) method. You can define your own visitor class that derive from the ExpressionVisitor class, which allows us to just look on it’s nodes or create replacements nodes.

For example, we can implement a (simplified) constant folding visitor:

class ConstantFolder : ExpressionVisitor
{
    protected override Expression VisitBinary(BinaryExpression node)
    {
        if (Visit(node.Left) is ConstantExpression left
            && Visit(node.Right) is ConstantExpression right)
        {
            switch (node.NodeType)
            {
                case ExpressionType.Add:
                    if (left.Type == typeof(int) && right.Type == typeof(int))
                    {
                        return Expression.Constant((int)left.Value + (int)right.Value);
                    }
                    break;
            }
        }

        return base.VisitBinary(node);
    }
}

Now we can create it and do some basic constant folding:

var expression = Expression.MakeBinary(ExpressionType.Add, Expression.Constant(1), Expression.Constant(1));
var constantFolder = new ConstantFolder();
var folded = constantFolder.Visit(expression);

Console.WriteLine(expression);  // (1 + 1)
Console.WriteLine(folded);      // 2

Instead of scattering methods in different classes for different tree node types (the virtual Accept method), the Accept method dispatch the call to different methods (like VisitABC, VisitXYZ, and etc.) of the visitors. Thus one visitor can react to all node types within one class for an algorithm.

Strategy Pattern

Speaking of Linq, we have a lot of extension methods that take delegates to work with objects that implements the IEnumerable<T> interface, such as Where, Select, and SelectMany. Take Where for example, it filters the collection according to a predicate:

var array = Enumerable.Range(0, 10).ToArray();      // [0, 1, 2, ..., 9]
var even = array.Where(x => x % 2 == 0).ToArray();  // [0, 2, 4, 6, 8]
var odd = array.Where(x => x % 2 == 1).ToArray();   // [1, 3, 5, 7, 9]

Strategy pattern allows users to have some choice within an algorithm.

Singleton Pattern

Singleton is a pattern where we only allow 1 object of its type is created and used.

In my experience I created certain IEqualityComparer<T> as singleton, such as CLRSignatureComparer for dictionary to compare symbols. For comparers, most of the time they do not store states, thus they can be used as singletons to avoid allocations.

Let’s create a simple example:

class ReferenceEqualityComparer<T> : IEqualityComparer<T>
{
    private ReferenceEqualityComparer<T>() { }

    public static ReferenceEqualityComparer<T> Instance { get; } = new ();

    public bool Equals(T x, T y) => ReferenceEquals(x, y);
    public int GetHashCode(T obj) => obj.GetHashCode();
}

Now we can pass this singleton as a Strategy to the constructor of a HashSet<T>:

var hashset = new HashSet<T>(ReferenceEqualityComparer<T>.Instance);

As we see, singltons can implement interfaces, and be passed as arguments, where static classes cannot.

Adapter Pattern

This pattern was brought up later in another question. Suppose we want to use a 3rd party library to send email. We want to create our own interface for our task, and create a class that implement the interface using codes from this library.

interface ISendEmail
{
    Task SendEmailAsync(string sender, string receiver, string message);
}

class XYZLibaryEmailSender : ISendEmail
{
    public async Task SendEmailAsync(string sender, string receiver, string message)
        => await XYZLibrary.SendAsync(sender, receiver, message);
}

Then we use dependency injection to have this interface injected to our code.

builder.Services.AddTransient<ISendEmail, XYZLibraryEmailSender>();

When we decide not to continue use the library because of various reasons like securtity, we can swap it to another library by replacing the builder.Serivces.Add.. above. Here the XYZLibaryEmailSender class we created is called an Adapter.


Design patterns in real world
https://blog.bigpower.dev/Design-patterns-in-real-world/
Author
Paul Chen
Posted on
August 6, 2022
Licensed under