DEV Community

Cover image for Adapter Design Pattern
Pranil Tunga
Pranil Tunga

Posted on

Adapter Design Pattern

The majority of the patterns that fall under the "creational" gamma category, or patterns that are primarily focused on the creation of structures, were covered in our previous blogs. Now that it is time to move on to the very next category, we will discuss the patterns that fall under the "structural" gamma category. So let's take a look at the "Adapter Design Pattern," which is our first structural pattern.


What is this pattern about?

You might be confused by reading the overall definition of this pattern, but don't worry; in the following sections, we will try to simplify it and explain it using a variety of examples. This pattern primarily focuses on interfaces, and its main goal is to obtain a new interface from an existing interface that you already have.

Various types of adapters.

Now imagine you are traveling internationally with an electrical device. Since we are aware that voltages and current parameters can vary from country to country and we are unable to really replace the entire electrical device, what we do is purchase an adapter that can adjust the voltage and other factors to allow us to use our devices without any problems.

The same principles apply to programming as well; occasionally, we can introduce a component into our system that can provide an additional interface to an already-existing interface, which we can then modify in accordance with business logic.

Why Adapters?

Let's say that when developing systems, we occasionally need to conform existing interfaces to the interfaces needed for new functionalities. The piece of software we write to accomplish this is simply called the "Adapter."

The main purpose of adapters is to enable legacy applications to work with new codebases so that we can reuse the existing code or library through newly created adapters.

Let's use an illustration to better understand:

namespace DesignPatterns.Structural.AdapterPattern
{
    public enum EmployeeTypes
    {
        Salaried,
        Contract
    }

    public class Employee
    {
        public string Name { get; set; }
        public double Salary { get; set; }

        public virtual void Display()
        {
            Console.WriteLine($"{Name} is employee");
        }
    }

    public class EmployeeAdapter : Employee
    {
        public EmployeeAdapter(string Name, EmployeeTypes employeeType)
        {
            this.Name = Name;
            var calculator = new EmployeeSalaryCalculator();
            this.Salary = calculator.Calculate(employeeType);
        }

        public override void Display()
        {
            Console.WriteLine($"{Name} is working on {Salary} per month");
        }
    }

    public class EmployeeSalaryCalculator
    {
        public double Calculate(EmployeeTypes type)
        {
            switch (type)
            {
                case EmployeeTypes.Salaried: return 10000;
                case EmployeeTypes.Contract: return 7000;
                default: return 0;
            }
        }
    }

    public class AdapterPattern
    {
        public static void Main(string[] args)
        {
            Employee john = new EmployeeAdapter("John", EmployeeTypes.Salaried);
            john.Display();

            Employee doe = new EmployeeAdapter("Doe", EmployeeTypes.Contract);
            doe.Display();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output of above code snippet.

As you can see, we have a class called "Employee" that acts as the employee's representation in our system, as well as a class called "EmployeeSalaryCalculator" that calculates the employee's salary. As you can see, we have now added a new class called "EmployeeAdapter" that acts as a sort of intermediary and adds extra functionality between these two classes. In other words, we are adding a new interface to modify an existing interface in order to achieve new functionality, and the new class is in charge of figuring out salaries for various employee types, so we are merely introducing a new structure to support new features.

The class "EmployeeSalaryCalculator" is an "Adaptee" in the example above, which is the class that an adapter uses to mold data, and the class "EmployeeAdapter" is an actual adapter class that we have added to support salary calculations for a variety of types.


Types of Roles in Adapter Patterns:

ITarget

This typically represents the interface in the codebase that the client wants to target or use.

Adaptee

This stands for the piece of code or interface that needs to be modified in order to accommodate the new changes.

Adapter

In terms of the Adaptee, this represents the class that actually implements the ITarget interface, making it the class most in charge of updating the structure or, to be more precise, introducing the new functionalities so that Adaptee can be used as an ITarget after being processed by an adapter.

Request

The lifecycle for adapter acceptance begins with this, which typically represents the actual operation the client wants to accomplish. It is also the stage at which an adapter's functionality is determined.

SpecificRequest

This determines what needs to be changed in Adaptee in order to achieve new functionality since it represents the actual implementation of the request's functionality in the Adaptee.


Different Implementations:

Adapters can be implemented in a variety of ways. Since we already looked at one type of implementation in the example above, let's move on to some other ways to define an adapter.

Generic Adapter Implementation:

This is another way we can make adapters; typically, we encourage passing generics to adapter classes in order to write more readable code. This adapter functions similarly to others, but people tend to follow these conventions for better readability and genericness.

Example:

namespace DesignPatterns.Structural.GenericAdapterPattern
{
    public abstract class Employee
    {
        public string Name { get; set; }
        public double Salary { get; set; }

        public abstract void CalculateSalary();
    }

    public class SalariedEmployee : Employee
    {
        public override void CalculateSalary()
        {
            Salary = 10000;
            Console.WriteLine($"{Name} working full time with ${Salary} per month");
        }
    }

    public class ContractBasisEmployee : Employee
    {
        public override void CalculateSalary()
        {
            Salary = 7000;
            Console.WriteLine($"{Name} working on contract basis with ${Salary} per month");
        }
    }

    public class EmployeeAdapter<T> where T : Employee, new()
    {
        public static void Display(string Name)
        {
            var obj = new T();
            obj.Name = Name;
            obj.CalculateSalary();
        }
    }

    public class AdapterPattern
    {
        public static void Main(string[] args)
        {
            EmployeeAdapter<SalariedEmployee>.Display("John");
            EmployeeAdapter<ContractBasisEmployee>.Display("Doe");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output of above code snippet.

As you can see, we are now using generics and passing classes directly to the EmployeeAdapter class, allowing it to select the class to use for runtime salary calculation.

Also, you can make a single method generic if you are not comfortable making the entire class generic and only want to make a specific method generic. This will allow you to introduce other things into the same class if you so choose.

This is a method to accomplish that:

public class EmployeeAdapter
{
    public static void Display<T>(string Name) where T : Employee, new()
    {
        var obj = new T();
        obj.Name = Name;
        obj.CalculateSalary();
    }
}

public class AdapterPattern
{
    public static void Main(string[] args)
    {
        EmployeeAdapter.Display<SalariedEmployee>("John");
        EmployeeAdapter.Display<ContractBasisEmployee>("Doe");
    }
} 
Enter fullscreen mode Exit fullscreen mode

That concludes this blog's discussion of the "Adapter Pattern," the first pattern in the "Structural" category. However, in future blogs, we will discuss other structural patterns as well. Follow along.

Happy Coding…!!!

Top comments (0)