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 Chain of Responsibility design pattern is a behavioral design pattern used to process a request through a sequence of potential handlers. Each handler in the chain can either process the request, pass it along to the next handler, or stop further processing.
1.Decoupling of Senders and Receivers: The pattern allows the sender of a request to pass it along a chain of handlers without needing to know which handler will ultimately process the request. 2.Dynamic Composition: Handlers can be added or removed dynamically at runtime, providing flexibility. 3.Request Handling: Each handler has the opportunity to:
- Process the request and stop further propagation.
- Partially handle the request and pass it to the next handler.
- Pass the request without handling it.
- Handler Interface/Abstract Class:
- Declares a method to handle the request.
- Optionally includes a reference to the next handler in the chain.
- Concrete Handlers:
- Implement the handler interface or extend the abstract class.
- Decide whether to process the request or pass it to the next handler.
- Client:
- Initiates the request by sending it to the first handler in the chain.
Advantages: 1.Flexibility: Handlers can be easily added, removed, or reordered. 2.Responsibility Sharing: Complex requests can be handled by splitting responsibilities across multiple handlers. 3.Open/Closed Principle: Adding new handlers doesn’t require modifying existing code.
Disadvantages: 1.Debugging Complexity: It can be harder to trace how a request is processed as it flows through the chain. 2.Potential Performance Hit: If the chain is long and no handler processes the request, performance may suffer.
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 State pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. It provides a way to encapsulate state-specific behavior and delegate behavior changes to the state objects themselves. This pattern is particularly useful when an object can have multiple states, and its behavior needs to vary based on the current state. Instead of using a lot of conditional logic (e.g., if-else or switch statements), the State pattern organizes the behavior into separate state classes.
- Context:
- The main class whose behavior changes based on its current state.
- It holds a reference to a State object that represents its current state.
- State Interface:
- Defines a common interface for all possible states of the context.
- Each state implements this interface to provide its specific behavior.
- Concrete States:
- These are the specific implementations of the State interface.
- Each class represents a particular state and defines its behavior.
- Game Development:
- Characters with different states like Idle, Walking, Running, Attacking.
- The state determines what actions the character can perform.
- Traffic Light System:
- A traffic light can be in Green, Yellow, or Red state.
- The behavior of transitioning lights is encapsulated in the state classes.
- Workflow Management:
- A document can be in states like Draft, UnderReview, Approved.
- Actions like editing, submitting, or publishing depend on the current state.
- Vending Machine:
- States like Idle, HasMoney, DispensingItem, OutOfStock.
- Each state controls valid operations for the vending machine.
- Authentication Flow:
- States such as LoggedOut, LoggedIn, Locked.
- Actions like Login, Logout, or Lock depend on the current state.
- Open/Closed Principle: Adding new states does not require changing the existing state logic.
- Simplifies Context Class: The context delegates the behavior to state objects, reducing its responsibilities.
- Easier to Understand: Each state has its own class, making it easier to manage and maintain.
- Increased Number of Classes: Each state requires a separate class, which can increase the complexity of the codebase.
- Tight Coupling: Context and state classes are tightly coupled, as context often needs to know about all states.
- Overhead: If there are very few states or if state transitions are rare, the overhead of implementing this pattern might not be justified.
- When an object’s behavior depends on its state and changes dynamically.
- When you need to eliminate large if-else or switch statements for state-dependent behavior.
- When state-specific behavior needs to be reused or extended easily.
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).
The Visitor pattern is a behavioral design pattern that allows you to add further operations to objects without modifying their structure. It does this by separating operations from the objects on which they operate. The pattern involves creating a Visitor interface with visit methods for each type in an object structure. Classes that want to support new operations “accept” a visitor, allowing it to perform actions on them. This pattern is especially useful when you want to add new operations to classes that have an existing hierarchy without modifying the classes themselves.
- Visitor Interface: Defines methods for each concrete type in the hierarchy. Each visit method represents an operation that can be performed on that type.
- Concrete Visitor: Implements the Visitor interface, providing specific implementations of each visit method. These are the actual operations performed on elements in the object structure.
- Element Interface: Declares an accept method that takes a Visitor as an argument. This method allows a visitor to access the element.
- Concrete Elements: Implement the Element interface and the accept method. When accept is called, it passes the current instance to the visitor’s corresponding visit method.
- Object Structure: Usually a collection of elements, such as a list or tree, that can be iterated over. Each element in the structure “accepts” a visitor, which performs its operation.
- Open/Closed Principle: You can add new operations without changing the existing class structure.
- Single Responsibility Principle: Operation logic is extracted from classes, reducing their responsibilities.
- Extensibility: Adding new visitors is straightforward and does not require modifying existing elements.
- Dependency on Object Structure: The visitor depends on specific elements in the structure, so changing the element hierarchy can require updates to all visitors.
- Difficulty with Encapsulation: If certain internal states of elements need to be protected, exposing them to visitors can violate encapsulation.
- Syntax Tree Traversal in Compilers
- File System Operations
- Validation Frameworks
- Game Development