What is the Structural Design Pattern in C#?
Structural design patterns play a crucial role in simplifying software development by providing a straightforward approach to understanding the connections between components. In essence, these patterns are predominantly employed for organizing class structures, managing interfaces, and defining the interactions among different classes.
When to use ?
In live applications, there may arise a need to adjust the composition of a class or the associations among its classes. However, it is crucial that such modifications do not disrupt the ongoing project. Moreover, the linkage of one Product to many Users is established by incorporating the Product class within the User class.
The configuration of these two classes or their associations will be altered tomorrow. The Structural Design Pattern proves to be useful in such scenarios.
What is the Adapter Design Pattern?
A design pattern known as the Adapter pattern enables objects with incompatible interfaces to work together. This approach proves beneficial when you want to leverage existing classes that do not align with your required interfaces.
Serving as a connector between two entities that are not inherently compatible is the Adapter Design Pattern. Let's consider A as the initial entity and B as the second one. The Adapter facilitates the usage of specific services from object B by object A. In this scenario, the Adapter acts as an intermediary or link between entities A and B. Object A interacts with the Adapter, which then interacts with object B after making any necessary adjustments or transformations.
Example:
Next, we are going to analyze the UML diagram of the adapter design pattern in comparison to our specific instance. Take a look at the illustration below. In this visual representation, you can observe two systems, or to be more precise, two interfaces. On the right-hand side, you can see the Third Party Billing System, whereas on the left-hand side, there is the Client, also known as the Current HR System. Our focus will now shift towards understanding the disparities existing between these two systems and exploring the utilization of C#'s Adapter Design Patterns to align them effectively.
The CSS code snippet below demonstrates the styling for a placeholder diagram:
.placeholder-diagram { background: linear-gradient(135deg, #374151 0%, #1f2937 100%); border-radius: 12px; padding: 40px; margin: 20px 0; text-align: center; }
.placeholder-diagram .placeholder-icon { font-size: 3rem; margin-bottom: 10px; }
.placeholder-diagram .placeholder-text { color: #9ca3af; font-size: 1rem; }
ProcessSalary is a functionality provided by the Third Party Billing System. The parameter List, containing the list of employees, will serve as the input for the ProcessSalary function. Subsequently, the function will iterate through each employee, calculate their salary, and transfer the funds to their bank account.
The employee information is stored as a string array on the left side or within the existing HR platform. The HR software aims to manage the processing of employee salaries. Subsequently, the HR system needs to invoke the ProcessSalary function of the Third Party Billing System.
However, upon reviewing the HR system, it becomes apparent that employee data is stored in a string array, whereas the ProcessSalary method of the Third Party Billing System necessitates the data to be in a List format. This mismatch between the List and string array formats prevents direct communication between the HR System and the Third Party Billing System, ultimately leading to their inability to operate concurrently.
How can we make to work together?
Employing the C# Adapter Design Pattern allows seamless interaction between the two systems or interfaces. Illustrated in the diagram below, an adapter is required to bridge the Third-Party Billing and HR systems.
The <style> element defines the styling for a placeholder diagram. This diagram typically includes a background color gradient, rounded borders, padding, margin, and centered text alignment. Within the diagram, there is an icon with a specified font size and a text section with a specific color and font size. The overall design is intended to enhance the visual representation of a placeholder within a user interface.
The human resources (HR) system is set to transmit the employee information to the adapter in the form of a String Array. The adapter's task is to extract the information from the string array, populate the employee object, and append each employee object to the List collection. Following this, the Adapter will pass the List to the ProcessSalary function of the Third-Party Billing System. The ProcessSalary function calculates the salary for each employee using a specific technique and transfers it to the respective employee's bank account.
Therefore, the Adapter Design Pattern can be applied in C# to enable the collaboration of two interfaces that are not originally compatible. Once more, there are two methods for incorporating the Adapter Design Pattern in C#.
Implementation:
Next, we will explore the implementation of the Object Adapter Design Pattern in C#.
Step1: Creating Employee Class
The code provided needs to be duplicated and inserted into a class file named Employee.cs. Both the Adapter and ThirdPartyBillingSystem (referred to as Adaptee) will make use of this class. Initially, we create the Employee object with the required arguments, followed by utilizing the class constructor to set the properties accordingly.
Step 2: Creating Adaptee
The required features for the client will be integrated within this class. However, it is essential to enhance the compatibility of this interface for the customer's use. To implement this, generate a new class file named ThirdPartyBillingSystem.cs and incorporate the following code. The ProcessSalary function within this class is responsible for handling the salary calculation for each employee upon accepting a list of employees as a parameter.
Step 3: Creating ITarget interface
In simpler terms, create an interface named ITarget.cs and insert the following code into it. This interface contains the abstract ProcessCompanySalary function that will be implemented by the Adapter. Once again, the client will utilize this method to handle the salary processing.
Step 4: Create an Adapter
Therefore, generate a class file named EmployeeAdapter.cs and insert the provided code into it. Within this class, you will find the implementation of the ProcessCompanySalary method along with the ITarget interface. Furthermore, this class contains a reference to the ThirdPartyBillingSystem (Adaptee) object. When the ProcessCompanySalary method is invoked, it initially accepts employee information in the form of a string array, which it subsequently converts into a list of employees. Subsequently, this list of employees is forwarded as a parameter to the ProcessSalary method of the ThirdPartyBillingSystem (Adaptee) object.
Step 5: Client
Our Human Resources System, acting as the primary component of the program class, will play the role of the client in this scenario. Please modify the Main method according to the following instructions. It is evident that the details of the employees are presently kept in a string array. Subsequently, we instantiate an EmployeeAdapter object and call the ProcessCompanySalary method, passing the string array as an argument. Consequently, the Third Party Billing System and the Client engage in cooperation facilitated by the Adapter, which is referred to as the EmployeeAdapter object.
UML Diagram :
Please consult the provided illustration to understand the Class or UML diagram and the various components of the Object Adapter Design Pattern. In the diagram, it is evident that the client creates an Adapter object through the ITarget Interface and subsequently utilizes this object to interact with the Adaptee. The adapter serves as the component facilitating the interaction between two distinct, incompatible interfaces.
The CSS code snippet below demonstrates the styling for a placeholder diagram.
.placeholder-diagram { background: linear-gradient(135deg, #374151 0%, #1f2937 100%); border-radius: 12px; padding: 40px; margin: 20px 0; text-align: center; }
.placeholder-diagram .placeholder-icon { font-size: 3rem; margin-bottom: 10px; }
.placeholder-diagram .placeholder-text { color: #9ca3af; font-size: 1rem; }
There are four components involved in the Adapter Design Pattern, and they are presented in the following sequence:
Client: The sole element visible to the Client class is the one that implements the ITarget interface, such as the Adapter (specifically, the EmployeeAdapter).
The Adapter must implement this specific interface. The client can only see the class that implements this interface.
The adapter class facilitates the interaction between two systems or interfaces that are not compatible by implementing both the interface method and the ITrager interface.
Therefore, it is essential to adjust or convert it before the client can make use of it. This signifies that the Adapter will receive the request from the client, carry out the required transformations, and then contact the Adaptee.
This pertains to the C# Object Adapter Design Pattern. Moving forward, let's explore how to achieve a similar outcome in C# by implementing the Class Adapter Design Pattern.
Understanding Class Adapter Design Pattern in C#:
Here is an alternative approach to implementing the C# Adapter Design Pattern. The Adapter instances in this approach will extend the functionality of the Adaptee class while also implementing the ITarget interface. Consequently, the Adapter class will become a subclass of the Adaptee class. With this setup, the Adaptee's methods can be accessed directly through inheritance, eliminating the need to create a separate Adaptee reference variable.
The CSS code snippet below demonstrates the styling for a placeholder diagram.
.placeholder-diagram { background: linear-gradient(135deg, #374151 0%, #1f2937 100%); border-radius: 12px; padding: 40px; margin: 20px 0; text-align: center; }
.placeholder-diagram .placeholder-icon { font-size: 3rem; margin-bottom: 10px; }
.placeholder-diagram .placeholder-text { color: #9ca3af; font-size: 1rem; }
The class diagram remains consistent with the class diagram of the Object Adapter Design Pattern. By leveraging the Adaptee object reference held by the adapter, the adapter will invoke the methods of the adaptee within the context of the Object Adapter Design Pattern.
Implementation:
The execution mirrors the approach of the Object Adapter Design Pattern.
The distinctive part lies within the EmployeeAdapter class. Upon completing any required modifications or discussions, it is imperative to call the ProcessSalary method.
Therefore, to implement the C# Class Adapter Design Pattern, you need to make modifications to the EmployeeAdapter class as specified below. The EmployeeAdapter class is derived from the ThirdPartyBillingSystem class, acting as the Adaptee, while it also conforms to the ITarget interface.
When to use the Object Adapter Pattern and Class Adapter Pattern in C#?
For example, if implementing inheritance in a Java class is not feasible, you can employ the Object Adapter Design Pattern to ensure compatibility between the Java class and the Dot Net class. Conversely, if inheritance is permissible, both classes belong to the same project, and they are developed using the same programming language, it is advisable to opt for the Class Adapter Design Pattern.
Real-Time Applications?
- Reusing Existing Code: An adapter can fill the gap if you have classes that already exist and provide functionality that you need to use, but their interfaces differ from the ones your system uses.
- Developing the same Interface for Various Classes: When you wish to handle multiple classes consistently using the same interface, but they have various interfaces.
- Supporting Multiple Data Sources: This refers to the situation in which your application needs to process data uniformly while handling it from several sources (such as databases, file systems, and web services).
- Testing and Mocking: When real objects (such as database connections or external services) are difficult to use in a test environment, adapters can be used to build stubs or mocks for unit testing.
- Maintaining Backward Compatibility: Adapters can be used to keep older APIs or data models compatible with updated applications or libraries.
- Cross-Platform Compatibility: Used in situations when you must maintain consistency in the application code while supporting several environments or platforms.
Let's analyze each of these C# structural design patterns in more detail:
The Adapter Pattern is useful when you want to merge existing classes with different interfaces without modifying the original source code.
In the given example:
The existing class with a mismatched interface is referred to as the adaptee.
This design can be applied to repurpose current classes in a fresh setting without altering their code or to incorporate novel elements into an established system.
Composite Pattern:
The composite design pattern is employed to depict objects in hierarchical structures, allowing users to manage single objects and groups of objects uniformly.
In the given example:
The component functions as the interface for the entire object within the composition.
Leaf: This represents the leaf elements within the structure that do not have any children.
This represents a composite entity that can contain child elements and delegates tasks to them for executing their functionalities.
This technique is useful when there is a need to manage both singular entities and collections of entities uniformly, especially within hierarchical systems like file directories or graphical user interfaces (GUIs).
Decorator Pattern:
By employing the decorator pattern, you can dynamically enhance the functionality of specific objects without altering the behavior of other objects within the identical class. This technique serves as a flexible alternative to subclassing when introducing additional capabilities.
In the given example:
Component: This serves as the object interface where duties can be linked.
ConcreteComponent: This serves as an illustration of the fundamental object that can have additional responsibilities assigned to it.
The core class for decorators, containing a reference to a component, is known as the Decorator.
ConcreteDecorator: This class represents concrete decorators responsible for adding extra responsibilities to the Component.
This method is valuable when you want to incorporate or modify functionality without impacting the core structure of a set of classes, each with unique duties.
Conclusion:
In summary, structural design patterns in C# offer efficient methods to organize and manage relationships between classes and objects within your codebase. Integrating these patterns into your design enhances the maintainability, scalability, and comprehensibility of your codebase. Additionally, it aids in structuring your codebase effectively. By applying these principles, you can develop systems that are easier to build, maintain, and modify as needed. Understanding and implementing these structural design patterns can significantly enhance the quality and robustness of your C# projects, irrespective of the scale of the system or application under development.