Introduction
What are Design Patterns in Software Programming?
There are many definitions of the design pattern. For example, from Wikipedia:
“In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. Rather, it is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.”
Or according to the “Gang of Four”
“Design patterns represent the best practices used by experienced object-oriented software developers. Design patterns are solutions to general problems that software developers faced during software development. These solutions were obtained by trial and error by numerous software developers over quite a substantial period of time.”
We may conclude out of this definition that patterns solve problems, or show the way how to solve complex problems with a simple solution.
According to Gang of Four, we can categorize patterns in the following categories:
- Creation: The creational patterns aims to separate a system from how its objects are created, composed, and represented. They increase the system’s flexibility in terms of the what, who, how, and when of object creation.
- Behavior: Behavior patterns are also referred to as chains of behavior, highlighting their nature as a complex linking of simpler segments of behavior. They may be formed via the operant conditioning of various segments presented in the appropriate order.
- Structural patterns: Structural patterns are concerned with how classes and objects are composed to form larger structures. Structural class patterns use inheritance to compose interfaces or implementations.
In modern software development often they are so many other categories like Concurrency Patterns, Cloud Patterns, and many other examples. Based on my own experience, I think there is one more category with is: “Others”. These are patterns that help me be productive, write clean code, and efficiently and effectively make my code more readable or performed better.
Patterns do not need to be related to software development, for example, one of the patterns that I use to track time is from my college J. Tower A Simple Time Tracking Process That Works For Me in his blog post he talks about the very nice and convenient method for tracking time and managing your time while working. What I am trying to say is patterns can be anything that helps you solve specific problems. In the next section, I will show you what I called the Bucket pattern and how it helped me write more readable code.
Bucket Pattern
Problem
The problem that the bucket pattern is trying to solve is how messy the injection of dependencies can be with many dependencies. In order to write cleaner and more appealing code, we can provide a class with its dependencies through a “bucket” filled with common objects.
For example, sometimes the list of dependencies injected into a controller class in ASP.NET Core can grow exponentially over time, and this tens to be very messy. On other hand, dependency injection needs to inject all those “objects” into the class, and in .NET there is currently no way to do delayed creation (instantiation) through the DI container. So, what should you do if you do not need all of those dependencies in each of your controller class methods?
Solution
Instead, let’s organize dependencies by category. I called them “buckets”, where each bucket is a category. How can we do this without making DI inject all unnecessary graph objects into the bucket?
The solution is to leverage Lazy<T>. The core feature of Lazy<T> is that it provides delayed creation which is thread-safe.
Practical Example
Let’s see how we can reduce complexity by injecting only one “bucket”.
Structure of the project:
I will be focusing on the interface IRepoBucket and RepoBucket class.
In IRepoBucket we define the interface of the bucket or common interface that we want to be accessible once the bucket is injected.
using BucketPattern.Interface; namespace BucketPattern.Buckets; public interface IRepoBucket { IRepository1 Repo1 { get; } IRepository2 Repo2 { get; } IRepository3 Repo3 { get; } IRepository4 Repo4 { get; } IRepository5 Repo5 { get; } IRepository6 Repo6 { get; } }
As you can see from the code there are only interfaces of Repository or some common objects.
Now let’s see how we can leverage Lazy<T> to have all interfaces when we need them without any dependency instantiated until we need it.
using BucketPattern.Interface; using Microsoft.Extensions.DependencyInjection; namespace BucketPattern.Buckets; public class RepoBucket : IRepoBucket { private readonly IServiceProvider _serviceProvider; private readonly Lazy<IRepository1> _lazyRepo1; private readonly Lazy<IRepository2> _lazyRepo2; private readonly Lazy<IRepository3> _lazyRepo3; private readonly Lazy<IRepository4> _lazyRepo4; private readonly Lazy<IRepository5> _lazyRepo5; private readonly Lazy<IRepository6> _lazyRepo6; public RepoBucket(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _lazyRepo1 = GetInstance<IRepository1>(); _lazyRepo2 = GetInstance<IRepository2>(); _lazyRepo3 = GetInstance<IRepository3>(); _lazyRepo4 = GetInstance<IRepository4>(); _lazyRepo5 = GetInstance<IRepository5>(); _lazyRepo6 = GetInstance<IRepository6>(); } private Lazy<T> GetInstance<T>() => new(_serviceProvider.GetRequiredService<T>()); public IRepository1 Repo1 => _lazyRepo1.Value; public IRepository2 Repo2 => _lazyRepo2.Value; public IRepository3 Repo3 => _lazyRepo3.Value; public IRepository4 Repo4 => _lazyRepo4.Value; public IRepository5 Repo5 => _lazyRepo5.Value; public IRepository6 Repo6 => _lazyRepo6.Value; }
Notice that I defined these lazy objects as read-only, and then defined the constructor of the class where I injected IServiceProvider. Once we need a repository service, the bucket will return an instance of that repository resolved by the ServiceProvider.
Let’s see how this looks when you use it:
using BucketPattern.Buckets; using BucketPattern.Interfaces; namespace BucketPattern.Implementation; public class Service : IService { private readonly IRepoBucket _repoBucket; public Service(IRepoBucket repoBucket) { _repoBucket = repoBucket; } public async Task<string> MethodAsync() => _repoBucket.Repo1.GetInfo(); }
From this code, you can see that we only injected IRepoBucket, and we have all repositories in one bucket, and the code looks much cleaner and readable.
Wrapping up
This simple pattern helped me write readable and cleaner code. I hope you find the use of it and as always. Happy coding!