Design patterns provide proven solutions to common software design problems. This repository is a comprehensive guide to understanding and applying design patterns using C#.
Design Patterns Covered :
- Singleton : Ensure a class has only one instance and provide a global point of access to it.
- Factory Method : Define an interface for creating an object, but let subclasses alter the type of objects that will be created.
- Abstract Factory : Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
- Builder : Separate the construction of a complex object from its representation so that the same construction process can create different representations.
- Prototype : Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
- Adapter : Convert the interface of a class into another interface clients expect.
- Bridge : Decouple an abstraction from its implementation so that the two can vary independently.
- Composite : Compose objects into tree structures to represent part-whole hierarchies.
- Decorator : Attach additional responsibilities to an object dynamically.
- Facade : Provide a unified interface to a set of interfaces in a subsystem.
- Flyweight : Use sharing to support large numbers of fine-grained objects efficiently.
- Proxy : Use sharing to support large numbers of fine-grained objects efficiently.
- Chain of Responsibility : Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.
- Command : Encapsulate a request as an object, thereby allowing for parameterization of clients with different requests.
- Interpreter : Define a representation for a language’s grammar along with an interpreter that uses the representation to interpret sentences.
- Iterator : Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
- Mediator : Define an object that encapsulates how a set of objects interact.
- Memento : Capture and externalize an object’s internal state so that the object can be restored to this state later.
- Observer : Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated.
- State : Allow an object to alter its behavior when its internal state changes.
- Strategy : Define a family of algorithms, encapsulate each one, and make them interchangeable.
- Template Method : Define the skeleton of an algorithm in a method, deferring some steps to subclasses.
- Visitor : Represent an operation to be performed on elements of an object structure.
The Singleton Pattern ensures a class has only one instance and provides a global point of access to that instance. It is one of the simplest design patterns, commonly used when exactly one object is needed to coordinate actions across the system.
The Factory Pattern is a creational design pattern to create objects without specifying the exact class of the object that will be created. Instead of using a direct constructor call to create an object, you use a factory method to produce an instance of the class. This pattern promotes loose coupling and makes the code more flexible and scalable.
The Abstract Factory pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. It allows you to produce a variety of objects that are related by a common theme, without having to know or specify the exact classes of those objects.
The Builder pattern is a creational design pattern that provides a way to construct complex objects step by step. It allows you to produce different types and representations of an object using the same construction process. The Builder pattern is especially useful when the construction of an object requires many steps or when there are many possible configurations for the object.
The Prototype pattern is a creational design pattern that allows you to create new objects by copying an existing object, known as a prototype, rather than by creating new instances through constructors. This pattern is particularly useful when the cost of creating a new object directly is expensive or complex.
The Adapter pattern is a structural design pattern that allows incompatible interfaces to work together by converting the interface of a class into another interface that a client expects. This pattern acts as a bridge between two incompatible interfaces, making it possible for classes with different interfaces to collaborate.
When to Use the Adapter Pattern:
The Bridge pattern is a structural design pattern that separates an abstraction from its implementation, allowing them to vary independently. This pattern is used to decouple an abstraction from its implementation so that the two can evolve separately without affecting each other. By using the Bridge pattern, you can avoid a permanent binding between an abstraction and its implementation, making your code more flexible and scalable.
When to Use the Bridge Pattern:
The Composite pattern is a structural design pattern used to compose objects into tree-like structures to represent part-whole hierarchies. It allows you to treat individual objects and compositions of objects uniformly. The Composite pattern enables clients to work with complex tree structures (comprising both individual objects and compositions) as if they were single objects, simplifying the client code.
When to Use the Composite Pattern:
The Decorator pattern is a structural design pattern that allows you to dynamically add behavior or responsibilities to individual objects, either statically or at runtime, without affecting the behavior of other objects from the same class. It provides a flexible alternative to subclassing for extending functionality.
When to Use the Decorator Pattern:
The Facade pattern is a structural design pattern that provides a simplified interface to a complex subsystem. It is used to hide the complexity of a system by providing a unified and simplified interface, making it easier for clients to interact with the subsystem without needing to understand its intricate details.
When to Use the Facade Pattern:
The Flyweight pattern is a structural design pattern that enables the efficient sharing of objects to support large numbers of fine-grained objects without incurring the overhead of storing a large amount of data. It is used to minimize memory usage and improve performance by sharing as much data as possible with similar objects, rather than keeping separate copies of data for each object.
The Proxy pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. The proxy object acts as an intermediary that represents the actual object, allowing you to add additional behavior or manage access to the original object without modifying its code.
When to Use the Proxy Pattern :
Types of Proxies:
- Virtual Proxy: Manages the creation and initialization of expensive objects. It acts as a stand-in and creates the real object only when it is needed.
- Remote Proxy: Represents an object that exists in a different address space (e.g., a different machine or network). It handles communication between the client and the remote object.
- Protection Proxy: Controls access to the real object, typically used for access control or authentication.
- Smart Proxy: Adds additional behavior when accessing the real object, such as reference counting, logging, or access monitoring.
The Interpreter pattern is a behavioral design pattern that defines a representation for a language’s grammar and provides an interpreter to process sentences in that language. This pattern is typically used to interpret expressions from languages with a defined grammar, such as mathematical expressions or simple scripting languages. The pattern is most useful when you need to evaluate or process expressions that can be defined in terms of a language or grammar. It works well for small languages where defining formal grammar rules and interpreting them is straightforward.
Advantages of the Interpreter Pattern :
- Flexibility: It provides an easy way to extend the grammar by adding new expression classes.
- Modularity: Each rule in the grammar is represented by a different class, making the design modular and easy to maintain.
- Reusability: Each expression (terminal or non-terminal) is reusable in different contexts and combinations.
Disadvantages of the Interpreter Pattern :
- Performance: It can become inefficient and slow if the language grammar is complex or the number of expressions grows large, as it requires recursive interpretation.
- Scalability: The pattern works best for small, simple languages or expressions. As the grammar grows, the number of classes increases, leading to higher complexity.
When to use Interpreter Pattern:
- Scripting languages: Small custom scripting languages where the grammar can be defined easily.
- Compilers/Interpreters: Parts of compilers or interpreters that parse and execute expressions.
- SQL/Mathematical Expression Evaluators: Systems that need to interpret mathematical formulas, queries, or commands.
- Configuration/Rule Engines: Applications that allow users to define rules in a specific language and then interpret and execute them.
The Iterator pattern is a behavioral design pattern that provides a way to access elements of a collection (like a list, array, or any aggregate object) sequentially, without exposing the underlying structure of the collection. This pattern is useful when you want to traverse through a collection without worrying about how it’s implemented, whether it’s an array, a linked list, or some other structure.
### Mediator:The Mediator pattern is a behavioral design pattern that aims to reduce the complexity of communication between multiple objects by introducing a mediator object. The mediator centralizes the communication logic, allowing objects (often referred to as “colleagues”) to interact indirectly through the mediator rather than directly with each other.
1. Reduced Coupling:The Memento design pattern is a behavioral design pattern that allows you to capture and store the current state of an object so that it can be restored later, without exposing the internal structure of that object. It is typically used to implement undo mechanisms in applications.
Key Components:
- Originator: The object whose state is being saved or restored. The Originator creates a Memento containing a snapshot of its current state.
- Memento: Stores the internal state of the Originator. It doesn't expose its data to other objects, thus preserving encapsulation.
- Caretaker: Manages the Memento. The Caretaker requests a Memento from the Originator and stores it, but does not access or modify the state. It later uses the Memento to restore the Originator to its previous state.
The Observer Pattern is a design pattern used in software engineering to establish a one-to-many dependency between objects. When the state of one object (the subject) changes, all its dependents (observers) are notified and updated automatically.
-
Subject: The object being observed. It maintains a list of observers and provides methods to attach and detach them.
-
Observer: An interface or abstract class defining the update method that will be called when the subject's state changes.
-
ConcreteSubject: A concrete implementation of the subject that maintains its state and notifies observers of changes.
-
ConcreteObserver: A concrete implementation of the observer that updates itself based on changes in the subject.
- User interface components (e.g., updating UI elements in response to data changes).
- Event handling systems.
- Real-time systems (e.g., stock market updates).
- Promotes loose coupling between the subject and observers.
- Enhances scalability and maintainability.
The Strategy Pattern is a behavioral design pattern that enables selecting an algorithm’s behavior at runtime. Instead of implementing a single algorithm directly, the code defines a family of algorithms, encapsulates each one as a separate class, and makes them interchangeable. The Strategy pattern lets you swap the algorithm or logic used within an object without altering the client code.
- When you have multiple ways to perform a task and want to switch between them easily.
- When you need to avoid conditional logic (if, switch) to select the algorithm.
- When you want to separate the algorithm logic from the context (the object using the algorithm).