In the software development process, it is common to face challenges and, as time passes, through the experience gained, it is noticed that some of these problems are recurrent in different projects. For the solution of some of these problems, there are code patterns that have already been tested. These mechanisms are known as Design Patterns.
Design Patterns are solutions to recurring problems that involve writing object-oriented code. This problem resolution is related to the modeling and structure of the code, helping for a more flexible architecture, making the code more testable, separated in responsibilities and with low coupling, thus respecting the SOLID patterns. It is then a high-level definition of how a common problem can be solved.
The purpose of these Design Patterns is not to reinvent the wheel, but to apply a solution with good code design.
This concept of patterns was introduced by four developers called “Gang of Four” (GoF) who wrote the book “Design Patterns: Elements of Reusable Object-Oriented Software” to catalog problems common to software development projects and how to solve them. Today this concept has 23 fundamental standards.
Some of these patterns are used a lot, and others not so much. There are currently more than 80 known patterns, which in general are variations of the 23 GoF patterns.
1- Design patterns are models that have been previously used and tested, so they can represent a good productivity gain for developers, shortening the way for new features.
2- Using these patterns also contributes to the organization and maintenance of projects, as these patterns are based on a low coupling between classes and standardization of the code.
3- Besides, with standardized terms, technical discussions are facilitated. Citing the name of a design pattern is more practical than explaining its entire behavior.
They provide ways to create an object and how it will be instantiated. Some of the most used patterns of this type are Abstract Factory, Factory Method, and Singleton. Below is a snippet of code that represents the Factory Method pattern:
Factory Method: Creates an instance of several class derivations. The abstract class can provide a standard object, but each subclass can instantiate an extended version of the object.
using System; namespace DoFactory.GangOfFour.Factory.Structural { /// <summary> /// MainApp startup class for Structural /// Factory Method Design Pattern. /// </summary> class MainApp { /// <summary> /// Entry point into console application. /// </summary> static void Main() { // An array of creators Creator[] creators = new Creator[2]; creators[0] = new ConcreteCreatorA(); creators[1] = new ConcreteCreatorB(); // Iterate over creators and create products foreach (Creator creator in creators) { Product product = creator.FactoryMethod(); Console.WriteLine("Created {0}", product.GetType().Name); } // Wait for user Console.ReadKey(); } } /// <summary> /// The 'Product' abstract class /// </summary> abstract class Product { } /// <summary> /// A 'ConcreteProduct' class /// </summary> class ConcreteProductA : Product { } /// <summary> /// A 'ConcreteProduct' class /// </summary> class ConcreteProductB : Product { } /// <summary> /// The 'Creator' abstract class /// </summary> abstract class Creator { public abstract Product FactoryMethod(); } /// <summary> /// A 'ConcreteCreator' class /// </summary> class ConcreteCreatorA : Creator { public override Product FactoryMethod() { return new ConcreteProductA(); } } /// <summary> /// A 'ConcreteCreator' class /// </summary> class ConcreteCreatorB : Creator { public override Product FactoryMethod() { return new ConcreteProductB(); } } }
They manage the composition of objects by inheritance and interfaces for different functionalities. The most common are: Adapter, Facade, and Composite. The following code snippet illustrates what the Adapter pattern represents.
Adapter: Compatible objects of different interfaces. It is a smart way of adapting changes in the application, without changing the main class.
using System; namespace DoFactory.GangOfFour.Adapter.Structural { /// <summary> /// MainApp startup class for Structural /// Adapter Design Pattern. /// </summary> class MainApp { /// <summary> /// Entry point into console application. /// </summary> static void Main() { // Create adapter and place a request Target target = new Adapter(); target.Request(); // Wait for user Console.ReadKey(); } } /// <summary> /// The 'Target' class /// </summary> class Target { public virtual void Request() { Console.WriteLine("Called Target Request()"); } } /// <summary> /// The 'Adapter' class /// </summary> class Adapter : Target { private Adaptee _adaptee = new Adaptee(); public override void Request() { // Possibly do some other work // and then call SpecificRequest _adaptee.SpecificRequest(); } } /// <summary> /// The 'Adaptee' class /// </summary> class Adaptee { public void SpecificRequest() { Console.WriteLine("Called SpecificRequest()"); } } }
They deal with interactions and communication between objects in addition to the division of responsibilities. The following patterns can be mentioned here: Command, Strategy, and Observer. The next one will show a practical use case for the Observer pattern.
Observer: One way to notify changes to several classes. Every time the abstract class changes, the list of subscribed observers is notified.
Code file: Observer.cs
using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Observer.Structural { /// <summary> /// MainApp startup class for Structural /// Observer Design Pattern. /// </summary> class MainApp { /// <summary> /// Entry point into console application. /// </summary> static void Main() { // Configure Observer pattern ConcreteSubject s = new ConcreteSubject(); s.Attach(new ConcreteObserver(s, "X")); s.Attach(new ConcreteObserver(s, "Y")); s.Attach(new ConcreteObserver(s, "Z")); // Change subject and notify observers s.SubjectState = "ABC"; s.Notify(); // Wait for user Console.ReadKey(); } } /// <summary> /// The 'Subject' abstract class /// </summary> abstract class Subject { private List<Observer> _observers = new List<Observer>(); public void Attach(Observer observer) { _observers.Add(observer); } public void Detach(Observer observer) { _observers.Remove(observer); } public void Notify() { foreach (Observer o in _observers) { o.Update(); } } } /// <summary> /// The 'ConcreteSubject' class /// </summary> class ConcreteSubject : Subject { private string _subjectState; // Gets or sets subject state public string SubjectState { get { return _subjectState; } set { _subjectState = value; } } } /// <summary> /// The 'Observer' abstract class /// </summary> abstract class Observer { public abstract void Update(); } /// <summary> /// The 'ConcreteObserver' class /// </summary> class ConcreteObserver : Observer { private string _name; private string _observerState; private ConcreteSubject _subject; // Constructor public ConcreteObserver( ConcreteSubject subject, string name) { this._subject = subject; this._name = name; } public override void Update() { _observerState = _subject.SubjectState; Console.WriteLine("Observer {0}'s new state is {1}", _name, _observerState); } // Gets or sets subject public ConcreteSubject Subject { get { return _subject; } set { _subject = value; } } } }
On this website are listed several other types of Design Patterns, with examples of real-world code written in C#, UML diagrams, as well as their frequency of use.
It is worth noting that these standards should only be used with knowledge and, if necessary, as it results in greater code complexity, sometimes unnecessarily.
Knowing Design Patterns is extremely important in the development of any software today. The demand for computer services is increasingly demanding, and compliance with these standards raises the delivery quality level. The use of these mechanisms saves working time and helps to organize the source code, besides providing a common language during documentation and technical discussions. The ideal for a developer is not to decorate them all but to know that they exist and how it is possible to apply them.
Reference: Software Architecture Fundamentals Course, Desenvolvedor.io.
Featured Image from Freepik.