Referbruv!


Card image cap

Understanding and Implementing Adaptor Pattern in ASP.NET Core

Design Patterns  • Posted 4 months ago

Sometimes developing usable components of an application that communicate with legacy components or interfaces of other working systems require components which aren't compatible with each other to be integrated. In a real-world scenario take the example of a system which exposes a micro-USB port for communication, and we have another plugin component which needs to communicate with this system but has a USB port for its access. Now in such scenarios, we come across things called Adapters which have an end of one type (say a micro USB port in this case) and are open to the other side via another type (say a USB port for example). And such an adapter helps incompatible types of components integrate together to achieve a common goal. Similarly, when developing an application which needs to use an external component that has an unexpected interface for access, we use an "Adapter" component which joins the two otherwise different components for the overall functionality. The Adapter pattern falls under the Structural category of the design patterns.

"An Adapter is yet another component which bridges the compatibility or the requirements gap between two different components and help these work together without actually requiring any of them to change."

Some of the benefits of using an Adapter pattern can include:

  1. Reusability of components without having required to make changes
  2. Providing backward compatibility of legacy functionalities against newer implementations which have differences in their structure
  3. contribute to flexibility of functional components

The Adapter pattern involves four components in action:

  • The Client
  • The Adapter
  • The Adaptee and
  • The Target

The Adapter is the two-faced component which provides the bridging. The target and the Adaptee form the two ends of the Adapter component which otherwise are not compatible for communication. And the Client is the endpoint for this setup which is interested in the communication.

We can implement the Adapter pattern in two ways:

  1. The Object Adapter
  2. The Class Adapter

The Object Adapter Pattern:

In this approach, we extract an interface out of the Target component which is used by the Adapter component on one end. The Client is unaware of the adaptee being invoked within the Adapter and simply calls the method on the Adapter class for which it creates an object. The Adapter class internally creates an object of the Adaptee class and calls the adaptee method which is executed. Let's take the example of a User endpoint which has access to the UserRepo object. We have an endpoint which needs to return all the users who are also readers. We have a different Repository class which is responsible for all Reader operations and since a class can only have a single responsibility we cannot instantiate a ReaderRepo object within the UserController. Now we would need an Adapter which takes the form of a UserRepo class but invokes a ReaderRepo within and since it has a Responsibility (the conversion in this case) assigned to itself, it suits the purpose perfectly.

A typical Object Adapter implementation can look like below:

namespace ReadersApi.Providers.Adapters
{
    public class ObjectReaderAdapter : IUserRepo
    {
        IReaderRepo repo;
        public ObjectReaderAdapter(IReaderRepo repo)
        {
            this.repo = repo;
        }

        public List<User> GetUsers()
        {
            var readers = repo.GetReaders();
            var users = ReaderStore.Users;
            return users.Where(x => readers.Any(r => r.Id == x.Id)).ToList();
        }
    }
}

Where this class is registered in the Startup class as below:

namespace ReadersApi
{
    public class Startup
    {
        // This method gets called by the runtime. 
        // Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            .....
            services.AddSingleton<IReaderRepo, ReaderRepo>();
            services.AddSingleton<IUserRepo, ObjectReaderAdapter>();
        }
        ...
     }
}

And the Controller is injected of an instance of IUserRepo as below:

namespace ReadersApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        IUserRepo repo;
        public UserController(IUserRepo repo)
        {
            this.repo = repo;
        }

        [HttpGet]
        [Route("allreaders")]
        public List<User> GetReaders()
        {
            return repo.GetUsers();
        }
    }
}

Here the Controller class (the Client) doesn't know that the IUserRepo is implemented by the class ObjectReaderAdapter (the Adapter component) which internally converts the call for get users along with the get readers functionality. This is provided by the injected ReaderRepo instance (the Adaptee component). The IUserRepo interface in this case provides a base for the conversion (the Target).

The Class Adapter Pattern:

A Class Adapter on the other hand makes use of multiple inheritance to provide conversion functionality instead of depending on objects. The Adapter class implements the Target interface and also inherits the Adaptee class, and thus gains access to both the features it requires. The Client, unaware of the Adapter creates an object of the Target interface which is resolved to the Adapter class and invokes the functionality of the Target interface. Within the functionality of the Target interface which is now implemented by the Adapter class the base functionality from the Adaptee class, which the Adapter class inherits is called and appropriate result is returned. In this way, the Class Adapter executes the translation, without having required of any internal dependencies.

Coming back to the Readers scenario, the Class Adapter for the above logic can be constructed as shown below:

namespace ReadersApi.Providers.Adapters
{
    public class ClassReaderAdapter : ReaderRepo, IUserRepo
    {
        public List<User> GetUsers()
        {
            var readers = base.GetReaders();
            var users = ReaderStore.Users;
            return users.Where(x => readers.Any(r => r.Id == x.Id)).ToList();
        }
    }
}

We can observe that the ClassReaderAdapter inherits the ReaderRepo class and implements the IUserRepo interface, thereby gaining access to the GetReaders() method of the base ReaderRepo class and provides an implementation of the GetUsers() method which the client: the UserController.GetReaders() invokes.

This is registered in the Startup.cs as:

namespace ReadersApi
{
    public class Startup
    {
        // This method gets called by the runtime. 
        // Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            ....
            services.AddSingleton<IReaderRepo, ReaderRepo>();
            services.AddSingleton<IUserRepo, ClassReaderAdapter>();
            ....
        }
        ....
    }
}

And when we run this setup, the UserController constructor receives an instance of the ClassReaderAdapter class (which actually implements the IUserRepo interface) and the GetReaders() method invokes the repo.GetUsers() method which internally invokes the ClassReaderAdapter.GetUsers() method. The logic is executed here and the intersection of Readers and Users (the Users who are also Readers; the expectation) is returned.

In this way we can implement a simple Adapter pattern in ASP.NET Core by means of dependency injection containers.

Other Structural Patterns:

The Decorator Pattern

The Adapter Pattern (this)

The Proxy Pattern

Published 4 months ago

Sponsored Links
We use cookies to improve user experience. Learn More