Design Patterns - Adapter

13 april 2022 om 10:00 by ParTech Media - Post a comment

In our previous blogs on design pattern series, we had covered different creational design patterns such as Abstract Factory, Factory Method, Singleton, Builder, and Prototype. Today we will be covering a whole new category i.e, Structural design pattern. And in that, we are going to understand in depth about Adapter design pattern.

Table of contents

  1. Introduction to Structural Design Pattern
  2. Understanding Adapter Design Pattern
  3. Conclusion

Introduction to Structural Design Pattern

Previously, we had seen how to create objects using creational design patterns. But there are instances where one class is dependent on another.

For example, there is an address class where the properties related to storing addresses are present. And there is a student class where the Address class is being used to store the address of the students. In the future, if the developer decides to remove the dependency of Address class from Student, it should not have an impact or lead to rework of the existing code. To help avoid this situation, structural design patterns are used.

Structural design patterns provide a simple, flexible, and efficient way to assemble objects and classes in a larger structure.

Understanding Adapter Design Pattern

Imagine a scenario where there is a phone, and it has a type C port for both charging and connecting the headphones. However, don’t forget that we also have 3.5 mm jack headphones. In such cases, to fit the headphones with the phone, we need to add hardware that connects with the phone through type C and also takes 3.5 mm as input. We call this an adapter.

Based on this example, the adapter is the one that acts as a bridge between two incompatible components (the phone and the headphone). Similarly, in the programming world, the Adapter design pattern provides a solution to bridge two incompatible classes/interfaces.

The Adapter pattern consists of the following components

  • ITarget
  • Adapter
  • Adaptee
  • Client
using System;

namespace PARTECH_Adapter
{
    class Program
    {
        static void Main(string[] args)
        {
            var adapter = new Adapter();

            var client = new Client(adapter);
            client.MakeRequest();
            Console.ReadLine();
        }
    }

    public interface ITarget
    {
        void Functionality();
    }

    public class Adaptee
    {
        public void Operation()
        {
            Console.WriteLine("Operation is performed.");
        }
    }

    public class Adapter : Adaptee, ITarget
    {
        public void Functionality()
        {
            Operation();
        }
    }


    public class Client
    {
        private ITarget target;

        public Client(ITarget target)
        {
            this.target = target;
        }

        public void MakeRequest()
        {
            target.Functionality();
        }
    }


}

Here,

  • ITarget is the interface that provides the method signature, which, in turn, is consumed by the client to achieve the functionality.
  • Adaptee is the class that has the code to achieve the functionality the client expects. Unfortunately, it is not compatible with the Client's class expectations.
  • The adapter class implements the ITarget interface and inherits the Adapter class. This acts as the translator for clients to achieve the required functionality.
  • Client class is the exterior layer that needs incompatible adaptee functionality. The adaptee functionality is made compatible and consumed by the client with the help of the Adapter class.

Now that we have understood the template to implement the Adapter pattern let us consider a practical scenario and implement the same.

Consider a college database system that stores the information of students and faculties in different tables with different data. Now, there arises a situation where the system has to generate the name and stream of all the members of the college, which includes both Faculties and Students. Let's understand how to implement the Adapter design pattern for this scenario.

using System;
using System.Collections.Generic;
using System.Linq;

namespace PARTECH_Adapter
{
    class Program
    {
        static void Main(string[] args)
        {
            var adapter = new Details();

            var client = new InformationGenerator(adapter);
            client.GetDetails();
            Console.ReadLine();
        }
    }

    /// <summary>
    /// Class to store the information which is used by the Adaptee.
    /// </summary>
    public class BasicDetails
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public string Stream { get; set; }
    }

    public class Student : BasicDetails
    {
        public long CurrentYear { get; set; }

        public double GradePoints { get; set; }
    }

    public class Faculty : BasicDetails
    {
        public string Designation { get; set; }
    }

    /// <summary>
    /// Adaptee class where the actual data is present.
    /// </summary>
    public class Database
    {
        public List<Faculty> GetFacultyDetails()
        {
            return new List<Faculty>()
            {
                new Faculty() { Id=1, Name="Faculty1", Designation="C", Stream="Mechanical" },
                new Faculty() { Id=1, Name="Faculty2", Designation="C1", Stream="Electrical" },
                new Faculty() { Id=1, Name="Faculty3", Designation="C2", Stream="BioEngineering" },
            };
        }
        public List<Student> GetStudentDetails()
        {
            return new List<Student>()
            {
                new Student() { Id=1, Name="Student1", GradePoints=3.5, CurrentYear= 2, Stream="Mechanical" },
                new Student() { Id=1, Name="Student2", GradePoints=3.2, CurrentYear= 4, Stream="Electrical" },
                new Student() { Id=1, Name="Student3", GradePoints=3.1, CurrentYear= 2, Stream="BioEngineering" },
            };
        }
    }

    public interface Itarget
    {
        List<string> GetStreamMembers();
    }

    /// <summary>
    /// Adapter class which implements the interface and inherits the adaptee.
    /// </summary>
    public class Details: Database, Itarget
    {
        public List<string> GetStreamMembers()
        {
            var facultyData = GetFacultyDetails();

            var studentData = GetStudentDetails();

            var combinedInfo = facultyData.Select(x => $"Name : {x.Name}, Stream : {x.Stream}").ToList();

            combinedInfo.AddRange(studentData.Select(x => $"Name : {x.Name}, Stream : {x.Stream}"));

            return combinedInfo;
        }
    }

    /// <summary>
    /// Client class which consumes the Adapter
    /// </summary>
    public class InformationGenerator
    {
        private Itarget target;

        public InformationGenerator(Itarget target)
        {
            this.target = target;
        }

        public void GetDetails()
        {
            target.GetStreamMembers().ForEach(x => Console.WriteLine(x));
        }
    }
}

The above code provides the desired solution by implementing an adapter pattern.

  • The classes BasicDetails, Student, and Faculty are used to store the data and are consumed mainly by the adaptee class. These three classes are independent of this pattern implementation.
  • The Database class acts as the Adaptee, which has two methods that return Faculty and Student data.
  • Itarget is the interface that is implemented by the Adapter class (Details). Details class also inherits the Adaptee class (Database). The Adapter class gets the Student and Faculty information and translates it as required by the Client(InformationGenerator).
  • InformationGenerator acts as the Client here and consumes the interface Itarget and gets the desired result from the method - GetDetails.
  • Finally, in the main method, an object for Details is passed as input to InformationGenerator, and a Client object is generated. It calls the GetDetails method and generates the below output.

Conclusion

And that’s what an Adapter Structural design pattern is all about. In this post, we have also seen how to implement it in a real-world scenario. Stay tuned for the next blog on the Design Patterns series.

Nieuwste