Design Patterns in C#: Behavioral, Creational, and Structural
Design patterns are reusable and customizable solutions to problems that often arise in software design and development. The patterns you use and how you use them depend entirely on your own unique programs and problems. Learning design patterns will not only help you solve software design problems, but will make you a stronger developer and allow you and your team to communicate more efficiently and uniformly. Today, we’re going to take a look at some design patterns in C# and discuss their use cases and pros and cons.
- What is a design pattern?
- Behavioral design patterns
- Creational design patterns
- Structural design patterns
- Next steps for your learning
What is a design pattern?
Design patterns were made to tackle common problems that arise within software design and software development when working with real-world app development. Design patterns are like customizable templates that can be applied to your code regardless of your preferred programming language. While they aren’t meant to be directly copied and pasted into your code, they provide you with high-level conceptual information that can help you solve your software design problems.
The same design pattern won’t look the same when applied to two different programs. This is where the customization comes in. You can think of a design pattern as a blueprint, providing you a high-level plan to guide you through your software design process, but giving you the power to implement the solution in a way that best fits your needs.
Note: Design patterns are different from algorithms. Algorithms clearly define a set of steps to help you solve a problem, while design patterns provide high-level descriptions.
Why use design patterns?
There are many benefits to using design patterns within your software development environment. Some benefits include:
- They provide you with a set of dependable solutions to common problems
- They help you learn the fundamentals to solving problems
- They give you and your team a way to communicate with one another more efficiently
- They provide you with pre-made templates that you can customize according to your needs
- They are easy to maintain because they mitigate the variability that arises with system requirements
There are 23 Gang of Four (GoF) patterns that are considered the foundations for all other design patterns. They are separated into three main groups based on three common problem areas within software architecture: behavioral, creational, and structural.
There are many different types of design patterns that you can implement into your programs. Let’s take a look at nine of the 23 GoF design patterns in C# and .NET and discuss their use cases and pros and cons.
Behavioral design patterns
Behavioral design patterns are concerned with algorithms and assigning responsibilities to objects. First, we’ll take a look at the Chain of Responsibility design pattern.
Chain of Responsibility
The Chain of Responsibility pattern enables you to pass requests along a chain of handlers. When a handler receives a request, it decides whether to process the request or pass it to the next handler in the chain.
Here are some use cases of the Chain of Responsibility design pattern:
- Your set of handlers and their order are supposed to change at runtime
- You need to execute multiple handlers in a certain order
- Your program needs to process different types of requests in different ways, but you don’t know exact requests before
- You determine the order of request handling
- You can bring new handlers into the application without changing the existing code
- You can decouple classes that invoke operations from other classes that perform operations
- It’s possible that some of your requests may not get handled
The Command pattern turns requests into stand-alone objects containing all the information about the requests. This process allows you to:
- pass requests as method arguments
- delay or queue requests to be executed
- reverse previous operations
Note: You can use the Prototype design patterns if you need to save copies of your Commands.
Here are some use cases of the Command design pattern:
- You want to queue or schedule the execution of your operations, or you want to execute your operations remotely
- You want to reverse previous operations
- You want to configure objects with operations
- You can bring new commands into the application without changing the existing code
- You can defer the execution of operations
- You can undo and redo operations
- It’s possible you could overcomplicate your code because of the layer between senders and receivers
The Iterator pattern allows you to iterate through elements in a collection without exposing the underlying representation.
Here are some use cases of the Iterator design pattern:
- Your collection consists of complex data structures underneath, but you don’t want clients to see its complexity
- You want to reduce duplication of traversal code within your application
- You want to iterate through different data structures or the types of these structures are unknown to you before
- You can separate big algorithms into different classes
- You can pass new collections and iterators into the application without changing the existing code
- You can simultaneously iterate over the same collection
- You can delay iterations and continue them when necessary
- If you’re working with an application with simple collections, applying the iterator pattern may be too complex for it
Creational design patterns
Creational design patterns provide you with different object creation mechanisms to increase the flexibility and reusability of your code. We’re going to take a look at three common creational design patterns, starting with the Abstract Factory design pattern.
The Abstract Factory pattern allows you to create families of related objects without indicating their concrete classes.
Here are some use cases of the Abstract Factory design pattern:
- You need your code to work with families of related objects, but you don’t want your code to rely on concrete classes
- You want to extend or customize your standard components
- You can bring new variations of products into the application without changing the existing code
- You can pull the product creation code to one place
- You can avoid tight coupling between client code and products
- You could end up with overly complicated code because of the interfaces and classes that come with the pattern
The Builder pattern allows you to build complex objects one step at a time and produce different representations of an object using the same construction code.
Here are some use cases of the Builder design pattern:
- You want to be able to create different representations of objects
- You want to build complex objects
- You don’t want to cram multiple parameters into your constructors
- You can reuse your construction code when building different versions of products
- You can build objects one step at a time
- Your code may become complex from the new classes that come with the pattern
The Factory Method pattern allows you to create objects in a superclass and allows subclasses to change the type of objects that will be created. It’s a specialization of the Template Method, which defines the framework of an algorithm in a superclass and allows the subclasses to override steps of the algorithm without changing the framework.
Here are some use cases of the Factory Method design pattern:
- You don’t know the types and dependencies of objects your code works with
- You want to save your system’s resources and reuse existing objects instead of creating new ones
- You want to provide your library to users with the option to add to its components
- You need a lot of flexibility in your code
- You can move the product creation code into one place
- You can avoid tight coupling between the creator and the products
- Your code may become overly complicated because of all the subclasses within the pattern
Structural design patterns
The Adapter pattern enables collaboration between objects with different interfaces.
Here are some use cases of the Adapter design pattern:
- You want to use an existing class but its interface isn’t compatible with your existing code
- You want to reuse existing subclasses that lack common functionality and can’t be added to the superclass
- You want your legacy code to work with modern classes
- You can separate the interface from the business logic of your program
- You can bring new adapters into the application without changing the existing code
- Your code may become complex because of the interfaces and classes associated with the pattern
The Bridge pattern allows you to split large classes or sets of related classes into separate hierarchies that can be developed independently of one another.
Here are some use cases of the Bridge design pattern:
- You want to divide and organize a class with multiple versions of a specific functionality
- You want to extend a class in multiple independent ways
- You’re working with a cross-platform application or with multiple API providers
- You can make platform-independent applications and classes
- You can work with high-level abstractions
- You can bring new implementations and abstractions into the application and work with them independently
- If you apply the pattern to a cohesive class, it can complicate the code
The Composite pattern allows you to make objects into tree structures and work with those tree structures as individual objects.
Here are some use cases of the Composite design pattern:
- You need to work with tree-like structures
- You want the client code to treat simple and complex elements in the same way
- You can easily work with complex tree structures
- You can bring new kinds of products into the application without changing the existing code
- If you’re working with classes with different functionalities, it may be hard to find a common interface to work with
Next steps for your learning
C# design patterns provide you with customizable and reusable solutions to common software design problems. The patterns you will work with will be dependent upon your individual needs and programs. Once you learn more about the different design patterns, you can begin implementing them into your programs. There are many more design patterns to learn such as:
- Singleton design pattern