Design patterns are a set of reusable solutions that address common problems encountered in software design. These patterns are widely used by experienced object-oriented software developers as they represent best practices in the field. By providing standardized terminology, design patterns are tailored to specific scenarios, enabling the creation of flexible, reusable, and maintainable code.
![Software design]()
There are three main categories of design patterns.
- Creational Patterns: These patterns focus on object-creation mechanisms and aim to create objects in a way that suits the given situation. Examples of creational patterns include Singleton, Factory, Builder, and Prototype.
- Structural Patterns: These patterns deal with object composition and the relationships between entities. They provide solutions for organizing and managing complex structures. Examples of structural patterns include Adapter, Composite, Proxy, Flyweight, Facade, Bridge, and Decorator.
- Behavioral Patterns: These patterns address object collaboration and the delegation of responsibilities between objects. They define how objects interact and communicate with each other to achieve a specific behavior. Examples of behavioral patterns include Strategy, Observer, Command, Chain of Responsibility, State, Template Method, Iterator, and Mediator.
By understanding and applying these design patterns, software developers can enhance the quality and efficiency of their code, leading to more robust and maintainable software systems.
Design patterns offer numerous advantages that contribute to the creation of high-quality, maintainable, and scalable software systems.
Here are some of the key benefits.
Advantages of Design Pattern
- Reusability: Design patterns provide generalized solutions to common problems, enabling their reuse in various projects and contexts. This reusability saves time and effort in software development.
- Scalability: By using design patterns, systems can be designed to be more scalable. Patterns often incorporate principles that make it easier to extend and modify the system as requirements evolve.
- Maintainability: Design patterns promote clean and well-organized code, which enhances maintainability. Code that follows design patterns is easier to understand, modify, and debug.
- Efficiency: Design patterns can improve the efficiency of the development process by providing tested and proven development paradigms. This can result in faster development times and fewer bugs.
- Standardization: Design patterns establish a common vocabulary and set of best practices among developers. This standardization improves communication and collaboration within and between development teams.
- Encapsulation of Best Practices: Design patterns encapsulate industry best practices and solutions. By applying these patterns, developers can leverage the collective experience and wisdom of the software engineering community.
- Ease of Communication: Design patterns provide a shared language for developers. When developers use design patterns, they can easily communicate complex ideas and solutions using terms that are universally understood within the industry.
- Design Flexibility: Patterns like Strategy, State, and Visitor provide mechanisms to add behavior to classes in a flexible and extensible way. This allows for more adaptable and flexible code structures.
Examples of creational design patterns
Creational design patterns provide a way to abstract the process of instantiation. They assist in creating a system that is not dependent on the specific way its objects are created, composed, and represented. A creational pattern using class inheritance allows for variation in the instantiated class, while an object creational pattern delegates the instantiation to another object. These patterns offer a great deal of flexibility in terms of what is created, who creates it, how it is created, and when it is created.
There are two main themes that are consistently present in these patterns.
- They encapsulate knowledge about the concrete class used by the system.
- They conceal the details of how instances of these classes are created and assembled.
Implementation of Factory Pattern
The Factory Method Pattern establishes a protocol for generating an object while enabling subclasses to modify the kind of objects that will be produced. The Factory Method, also referred to as the Factory Design Pattern or virtual constructor, establishes an interface for creating objects, allowing subclasses to determine which class to instantiate. This pattern enables a class to defer instantiation to its subclasses. The object creation logic is encapsulated in a distinct method, which abstracts the instantiation process and promotes loose coupling between the creator and the created objects. By enabling subclasses to define their own implementation of the factory method to create specific types of objects, this pattern promotes flexibility, extensibility, and maintainability in the codebase.
When to utilize the Factory Method?
- A class is unable to predict the class of objects it needs to create.
- A class desires its subclasses to define the objects they create.
- Classes delegate responsibility to multiple helper subclasses, and there is a need to centralize the information regarding which helper subclass acts as the delegate.
- If you require a way to encapsulate the creation of objects, utilizing a factory method can simplify the client code and enhance reusability, especially in cases where the object creation process is complex or may vary based on conditions.
- To decouple client code from concrete classes, the Factory Method Pattern can be employed. This pattern enables the creation of objects through an interface or abstract class, abstracting the specific implementation details of the concrete classes from the client code. This approach promotes loose coupling and facilitates system modification or extension without impacting existing client code.
- In scenarios where multiple variations of a product need to be supported or new types of products may be introduced in the future, the Factory Method Pattern offers a flexible solution. By defining factory methods for each product type, these variations can be accommodated effectively.
- If customization or configuration support is required, factories can be utilized to encapsulate the configuration logic. This allows clients to customize the creation process by providing parameters or configuration options to the factory method.
Factory Pattern Components
- Product Interface: INotification
- Responsibility: Specifies the interface for the products that can be produced by the factory.
- Example: The INotification interface includes a SendNotification () method that is implemented by all notification types (EmailNotification, SMSNotification, PushNotification).
- Concrete Products: EmailNotification, SMSNotification, PushNotification
- Responsibility: Implements the INotification interface to define specific product types.
- Example: The EmailNotification, SMSNotification, and PushNotification classes implement the INotification interface and provide unique implementations for sending notifications via email, SMS, and push notifications.
- Creator: NotificationProcessFactory
- Responsibility: Defines the factory method (CreateNotificationObject) that generates instances of INotification.
- Example: The NotificationProcessFactory class has a CreateNotificationObject (NotificationType type) method to create and return instances of INotification based on the specified NotificationType.
- Concrete Creator (Factory Method Implementation)
- Responsibility: Implements the factory method to produce specific product instances.
- Example: The NotificationProcessFactory class implements the CreateNotificationObject method, which uses a switch statement to decide which type of notification (EmailNotification, SMSNotification, PushNotification) to create and return based on the provided NotificationType.
- Client Code
- Responsibility: Utilizes the factory method to generate products without requiring knowledge of the specific concrete classes being created.
- Example: The Program class (client code) creates an instance of NotificationProcessFactory and uses it to generate various types of notifications (EmailNotification, SMSNotification, PushNotification) based on the NotificationType enum. Clients interact with the factory through the CreateNotificationObject method, which shields them from the complexities of instantiation and allows for flexibility in changing the concrete product types without altering the client code.
![Notification Process Factory]()
I will present an actual example of this design pattern. Let's say we have to create a notification system that can send email, SMS, and push notifications. These are three types of objects, following the Single Responsibility Principle. In order to implement this system, we will use the factory pattern to obtain objects for all three, as they have similar functionality despite being different objects. In order to apply the Factory pattern, it is essential to comprehend the subsequent implementation.
Step 1. Develop an interface named "INotification" as indicated in the UML diagram.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPatterExamples.FactoryPattern
{
internal interface INotification
{
void SendNotification();
}
}
Step 2. Create concrete classes implementing the same interface.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPatterExamples.FactoryPattern
{
internal class EmailNotification : INotification
{
public void SendNotification()
{
Console.WriteLine("Sending Email Notification...");
}
}
}
namespace DesignPatterExamples.FactoryPattern
{
internal class SMSNotification : INotification
{
public void SendNotification()
{
Console.WriteLine("Sending SMS Notification...");
}
}
}
namespace DesignPatterExamples.FactoryPattern
{
internal class PushNotification : INotification
{
public void SendNotification()
{
Console.WriteLine("Sending push Notification...");
}
}
}
Step 3. Create a Factory to generate an object of concrete class based on the given information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPatterExamples.FactoryPattern
{
public enum NotificationType
{
Email,
SMS,
Push
}
internal class NotificationProcessFactory
{
INotification notification = null;
public INotification CreateNotificationObject(NotificationType type)
{
switch (type)
{
case NotificationType.Email:
notification = new EmailNotification();
break;
case NotificationType.SMS:
notification = new SMSNotification();
break;
case NotificationType.Push:
notification = new PushNotification();
break;
default:
throw new NotSupportedException($"Notification type '{type}' is not supported.");
}
return notification;
}
}
}
Step 4. Utilize the Factory pattern to instantiate a specific class object by providing relevant information, like the type.
using DesignPatterExamples.FactoryPattern;
using System.Windows;
namespace DesignPatterExamples
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BtnFactoryPattern_Click(object sender, RoutedEventArgs e)
{
NotificationProcessFactory factory = new NotificationProcessFactory();
// Send an Email Notification
INotification emailNotification = factory.CreateNotificationObject(NotificationType.Email);
emailNotification.SendNotification();
// Send an SMS Notification
INotification smsNotification = factory.CreateNotificationObject(NotificationType.SMS);
smsNotification.SendNotification();
// Send a Push Notification
INotification pushNotification = factory.CreateNotificationObject(NotificationType.Push);
pushNotification.SendNotification();
}
}
}
Description of the above implementation
- Notification: Interface that encompasses all notification types.
- EmailNotification, SMSNotification, and PushNotification: Specific implementations of the Notification interface.
- NotificationProcessFactory: Factory class responsible for generating instances of notifications based on the NotificationType enum.
- The CreateNotificationObject method: utilizes a switch statement to create the appropriate notification type according to the NotificationType.
- Client Code: Demonstrates the usage of NotificationFactory to produce and dispatch various notification types based on the NotificationType enum.
This approach follows the Factory Pattern by consolidating the creation process for different notification types within a single factory class (NotificationProcessFactory). Clients communicate with the factory through an enum (NotificationType) to specify the desired notification type, ensuring that the client code remains detached from the specific notification implementations.
Repository Path: https://github.com/OmatrixTech/DesignPatterExamples
To be continued.