Introduction
So basically, what are design patterns? We can define design patterns as proven techniques to achieve certain tasks. They give us a basic template to design our application using templates that have been proven to meet the requirements of performance, maintainability, extensibility, scalability and security. Object oriented programming is the backbone of software development these days and this holds true for C# development as well. Design Patterns: Elements of Reusable Object-Oriented Software (1994) is a software engineering book which describes software design patterns. The book was written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, with a foreword by Grady Booch. It describes 23 classic software design patterns and is regarded as an important source for object-oriented design theory and practice. The authors are often referred to as the Gang of Four (GoF). This book includes examples in C++ and Smalltalk. These design patterns have been divided into three categories and in this article, I will provide two samples from each category in C#.
The 3 Categories and 23 Design Patterns
For a complete list of all the 23 Design Patterns, you can visit the link (
The Gang of Four Design Patterns). In this article I will discuss two design patterns from each category which are below,
Creational Design Patterns
Creational patterns help us with creating objects in a flexible manner according to our requirements
- Singleton pattern – Only allows one instance of a class.
- Factory pattern - Creates objects based on an input parameter.
Structural Design Patterns
Structural patterns help us design interfaces and use inheritance to design functionality in our applications.
- Adapter pattern – Provides a link between classes that have incompatible interfaces
- Facade pattern - Provides a simplified interface to some complex classes
Behavioral Design Patterns
Behavioral patterns are mostly linked to communications between objects in our application.
- Observer pattern – This is a publisher-subscriber pattern which allows the publisher to publish events and subscribers to subscribe and act upon these events
- Template pattern – This provides a layout of a base class which is then used by several classes
We will now look at these six design patterns one by one with examples.
The Singleton Pattern
Below is a class designed using the singleton pattern,
- namespace DesignPatterns
- {
- public class SingletonClass
- {
- private int number = 100;
-
- private static SingletonClass _instance;
- public static SingletonClass Instance
- {
- get
- {
- _instance = (_instance ?? new SingletonClass());
- return _instance;
- }
- }
-
- public int GetSingletonValue()
- {
- return ++number;
- }
- }
-
- }
We can call the class using the below code,
-
- var singletonClassValueOne = SingletonClass.Instance.GetSingletonValue();
- Console.WriteLine($"First Read value is {singletonClassValueOne}");
-
- var singletonClassValueTwo = SingletonClass.Instance.GetSingletonValue();
- Console.WriteLine($"First Read value is {singletonClassValueTwo}");
When we run this application, we see the below results,
Hence, we see that only one instance of the class is created and that is the first time this class is called. After that, the existing instance is used.
The Factory Pattern
Below is a class designed using the Factory pattern,
- using System;
- using System.Collections.Generic;
- using System.Text;
-
- namespace DesignPatterns
- {
-
- public interface IThermostat
- {
- void Control();
- }
-
-
- public class CoolingThermostat : IThermostat
- {
- private readonly double _temp;
- public CoolingThermostat(double temp)
- {
- _temp = temp;
- }
- public void Control()
- {
- Console.WriteLine($"Cooling the house to {_temp} degrees");
- }
- }
-
-
- public class WarmingThermostat : IThermostat
- {
- private readonly double _temp;
- public WarmingThermostat(double temp)
- {
- _temp = temp;
- }
- public void Control()
- {
- Console.WriteLine($"Warming the house to {_temp} degrees");
- }
- }
-
-
- public abstract class ThemostatFactory
- {
- public abstract IThermostat Create(double temp);
- }
-
- public class CoolingThermostatFactory : ThemostatFactory
- {
- public override IThermostat Create(double temp) => new CoolingThermostat(temp);
- }
- public class WarmingThermostatFactory : ThemostatFactory
- {
- public override IThermostat Create(double temp) => new WarmingThermostat(temp);
- }
-
-
-
- public enum Actions
- {
- Cooling,
- Warming
- }
-
- public class Thermostat
- {
- private readonly Dictionary<Actions, ThemostatFactory> _factories;
-
- public Thermostat()
- {
- _factories = new Dictionary<Actions, ThemostatFactory>();
- foreach(Actions action in Enum.GetValues(typeof(Actions)))
- {
- var factory = (ThemostatFactory)Activator.CreateInstance(Type.GetType("DesignPatterns." + Enum.GetName(typeof(Actions),action) + "ThermostatFactory"));
- _factories.Add(action, factory);
- }
- }
-
- public IThermostat ExecuteCreate(Actions action, double temp) => _factories[action].Create(temp);
- }
-
- }
We can call the class using the below code,
-
- var factory = new Thermostat().ExecuteCreate(Actions.Cooling, 17.5);
- factory.Control();
-
- factory = new Thermostat().ExecuteCreate(Actions.Warming, 21.5);
- factory.Control();
When we run this application, we see the below results,
Here we see that we pass the Enum value of the class and get back an instance of the class instead of explicitly creating a class.
The Adapter Pattern
Below is a class designed using the Adapter pattern,
- using System;
- using System.Collections.Generic;
-
- namespace DesignPatterns
- {
- public class LocalEmployeeSystem
- {
- private ISource _employeeAdapter;
-
- public LocalEmployeeSystem(ISource employeeAdapter)
- {
- this._employeeAdapter = employeeAdapter;
- }
-
- public void ShowEmployeeList()
- {
- List<string> employees = _employeeAdapter.GetEmployeeList();
-
-
- foreach (var employee in employees)
- {
- Console.Write(employee);
- }
- }
- }
-
- public interface ISource
- {
- List<string> GetEmployeeList();
- }
-
- public class BIEmployeeSystem
- {
- public string[][] GetEmployees()
- {
- string[][] employees = new string[2][];
-
- employees[0] = new string[] { "100", "John Doe", "Director" };
- employees[1] = new string[] { "101", "Jane Doe", "Manager" };
-
- return employees;
- }
- }
-
- public class EmployeeAdapter : ISource
- {
- public List<string> GetEmployeeList()
- {
- List<string> employeeList = new List<string>();
-
- var BiEmployeeSystem = new BIEmployeeSystem();
-
- string[][] employees = BiEmployeeSystem.GetEmployees();
- foreach (string[] employee in employees)
- {
- employeeList.Add($"{employee[0]},{employee[1]},{employee[2]} \n");
- }
-
- return employeeList;
- }
- }
- }
We can call the class using the below code,
-
- var source = new EmployeeAdapter();
- var client = new LocalEmployeeSystem(source);
- client.ShowEmployeeList();
When we run this application, we see the below results,
Here we see that the Employee Adapter class converts the array of string from the BIEmployeeSystem class to a list of string which is required by the LocalEmployeeSystem class.
The Facade Pattern
Below is a class designed using the Facade pattern,
- namespace DesignPatterns
- {
- internal class EmployeePersonalDetails
- {
- internal int ID;
- internal string FirstName;
- internal string LastName;
- internal string Address;
- internal string ContactNumber;
- internal int Age;
-
- public EmployeePersonalDetails(int Id)
- {
-
-
-
- ID = 1;
- FirstName = "John";
- LastName = "Doe";
- Address = "100 Street West, Toronto, ON, Canada";
- ContactNumber = "100-100-100";
- Age = 35;
- }
- }
-
- internal class EmployeeSalaryDetails
- {
- internal int ID;
- internal double BaseSalary;
- internal double Tax;
-
- public EmployeeSalaryDetails(int Id)
- {
-
-
-
- ID = 1;
- BaseSalary = 3000.00;
- Tax = 300.75;
- }
- }
-
- public class EmployeeDetails
- {
- public int ID { get; private set; }
- public string FullName { get; private set; }
- public string Address { get; private set; }
- public string ContactNumber { get; private set; }
- public double NetPay { get; private set; }
-
- public EmployeeDetails(int id)
- {
- var _employeePersonalDetails = new EmployeePersonalDetails(id);
- var _employeeSalaryDetails = new EmployeeSalaryDetails(id);
- ID = id;
- FullName = $"{_employeePersonalDetails.FirstName} {_employeePersonalDetails.LastName}";
- Address = _employeePersonalDetails.Address;
- ContactNumber = _employeePersonalDetails.ContactNumber;
- NetPay = _employeeSalaryDetails.BaseSalary - _employeeSalaryDetails.Tax;
- }
-
- }
- }
We can call the class using the below code,
-
- var EmployeeDetails = new EmployeeDetails(100);
- Console.WriteLine($"Employee ID is {EmployeeDetails.ID}");
- Console.WriteLine($"Name is {EmployeeDetails.FullName}");
- Console.WriteLine($"Address is {EmployeeDetails.Address}");
- Console.WriteLine($"Contact Number is {EmployeeDetails.ContactNumber}");
- Console.WriteLine($"Net Pay is {EmployeeDetails.NetPay}");
When we run this application, we see the below results,
Here we see that we collect the data from two classes and present it through a third class, which is the façade class, as it is just a point to collect more complex data and present it.
The Observer Pattern
Below is a class designed using the Observer pattern,
- using System;
- using System.Collections;
-
- namespace DesignPatterns
- {
- public abstract class Publisher
- {
- private ArrayList observers = new ArrayList();
-
- public void AddObserver(IObserver o)
- {
- observers.Add(o);
- }
-
- public void RemoveObserver(IObserver o)
- {
- observers.Remove(o);
- }
-
- public void NotifyObservers()
- {
- foreach (IObserver o in observers)
- {
- o.Update();
- }
- }
- }
-
- public class UsablePublisher : Publisher
- {
- private string value;
-
- public string GetValue()
- {
- return value;
- }
-
- public void SetValue(string newValue)
- {
- value = newValue;
- NotifyObservers();
- }
- }
-
- public interface IObserver
- {
- void Update();
- }
-
- public class UsedObserver : IObserver
- {
- private UsablePublisher publisher;
-
- public UsedObserver(UsablePublisher pub)
- {
- publisher = pub;
- }
-
- public void Update()
- {
- Console.WriteLine($"The updated value is { publisher.GetValue()}");
- }
- }
- }
We can call the class using the below code,
-
- var usablePublisher = new UsablePublisher();
- var usedObserver = new UsedObserver(usablePublisher);
- usablePublisher.AddObserver(usedObserver);
- usablePublisher.SetValue("Updated Value!");
When we run this application, we see the below results,
Hence, we see that when the value is changed, the publisher notifies all observers and they take appropriate action, as required.
The Template Pattern
Below is a class designed using the Template pattern,
- using System;
-
- namespace DesignPatterns
- {
- public abstract class WriteArticle
- {
- public WriteArticle()
- {
- Introduction();
- MainBody();
- Examples();
- Summary();
- }
- public abstract void Introduction();
- public abstract void MainBody();
- public abstract void Examples();
- public abstract void Summary();
-
- }
-
- public class ArticleA : WriteArticle
- {
-
- public override void Introduction()
- {
- Console.WriteLine($"This is the introduction section of Article A");
- }
-
- public override void MainBody()
- {
- Console.WriteLine($"This is the main body section of Article A");
- }
-
- public override void Examples()
- {
- Console.WriteLine($"These are examples of Article A");
- }
-
- public override void Summary()
- {
- Console.WriteLine($"This is the summary section of Article A");
- }
- }
- public class ArticleB : WriteArticle
- {
- public override void Introduction()
- {
- Console.WriteLine($"This is the introduction section of Article B");
- }
-
- public override void MainBody()
- {
- Console.WriteLine($"This is the main body section of Article B");
- }
-
- public override void Examples()
- {
- Console.WriteLine($"These are examples of Article B");
- }
-
- public override void Summary()
- {
- Console.WriteLine($"This is the summary section of Article B");
- }
- }
- }
We can call the class using the below code,
-
- var articleA = new ArticleA();
- var articleB = new ArticleB();
When we run this application, we see the below results,
In this pattern, we design a layout of some actions and then each class that inherits from it, provides its own implementation, as required.
Summary
In this article, we have looked at 6 design patterns, two from each of the creational, structural and behavioral categories. We see that these templates provide a starting point for us to design our classes for a particular requirement. I would recommend you look at the remaining design patterns as well and try to implement these in your object-oriented applications.