Kayvan Alimohammadi

full-stack developer who loves software architecture, clean code and proper software design.

Adapter Design Pattern: Adapt Everything With Your Codes

When we start building software, we can see many code's incompatibilities. This is because of many codes have been written based on different contracts.
For example, in one method the type of an argument for a user is an integer (user ID) and in another method, an argument type is a class object (User class object).
In this situation, we encounter many problems and we should probably waste a lot of time to resolve these issues.

What is the adapter design pattern?

Adapter/Wrapper design pattern is one of the most practical and useful patterns that has use in many cases.
When you have two incompatible interfaces in your classes you can use the adapter pattern.
Or when you want to use some third-party package in your project the best practice is to wrap that package in an adapter/wrapper class.
Indeed the adapter design pattern converts external contracts to your contract and in this way, there is no need to change contracts in each side.
Unfortunately I've seen many developers changing a third-party package source code to make it compatible with their code and they don't consider the fact that they are violating the SOLID principles.
If that third-party owner wants to have some update for that package and you lost all overrode codes, then what is the solution?

Talk is cheap, show me the code.

Imagine there is a Purchase Service class that needs User and Order class object to perform and complete a purchase and this class has used in many other codes and all of them provide User and Order objects to this class.
namespace App\Services\Purchase;

class PurchaseService
{
    /**
     * instance of user class
     *
     * @var App\Models\User
     */
    private $user;
    /**
     * instance of order class
     *
     * @var App\Models\Order
     */
    private $order;
    /**
     * make a instance from class
     *
     * @return void
     */
    public function __construct(User $user, Order $order)
    {
        $this->user = $user;
        $this->order;
    }
}
Suppose there is a method in a specific controller which needs Purchase Service class and it only can provide a user ID and an Order ID.
What can we do for this situation? there are a few solutions listed below.
  1. convert that user ID and order ID to their relative objects.
  2. place some condition in our Purchase Service class to check arguments is an ID or is an object.
  3. use an Adapter class to adapt this incompatibility.
  4. Let's figure out these ways and how they can solve our issue.
    Converting user ID or order ID to an object is the most straightforward way but it forces extra responsibility to controller class or other any class that wants to do the same thing, so this method is irrational.
    Placing some conditions in our Purchase Service class leads to writing many if-else or switch statements and that is no optimized way to accomplish our goal.
    After a while, if we want to provide some other data type like email for users and order reserve number for Purchase Service then we need more if-else statements.
    And the final way is to use the Adapter pattern, we can make an Adapter class that converts that user ID and order ID to correspond object and then pass them to purchase service.
    namespace App\Services\Purchase;
    
    class PurchaseServiceAdapter
    {
        /**
         * instance of purchase service
         *
         * @var App\Services\Purchase\PurchaseService
         */
        private $purchaseService;
    
        public function __construct(int $userID, int $orderID)
        {
            $user = User::find($userID);
            $order = Order::find($orderID);
            $this->purchaseService= new PurchaseService($user, $order);
        }
        /**
         * get purchase service instance
         *
         * @return void
         */
        public function getPurchaseService()
        {
            return $this->purchaseService;
        }
    }
    So, anywhere you have data based on user ID and order ID you can use this Adapter and easily get a real object from Purchase Service class.

    Adapter Pattern As a Wrapper

    This is a special case of using the adapter design pattern for wrapping some classes or objects and then provide that for other code sections.
    In this case, imagine we've used a third-party package in our project and it's been used in many different places and after a while, we want to replace that package with another one.
    For example, the current package has deprecated or its creator doesn't have any plan to update it anymore.
    In this situation, the other side of the adapter pattern that means wrapper pattern can help us.

    Real-World Example of Wrapper Pattern

    in this example, let's suppose we've used GuzzleHttp as an HTTP request handler for our project.
    At first, if this class and ts other related stuff have used in anywhere by us or our team then replacing it with a new one is hard and painful.
    To prevent this disaster from happening we can make a wrapper class for this client and tell every developer who contributes to the project to use this wrapper class instead of using GuzzleHttp directly in their codes.
    namespace App\Handlers\Http;
    
    use Psr\Http\Message\ResponseInterface;
    
    class HttpHandler
    {
        /**
         * handler instance
         *
         * @var $handler
         */
        private $handler;
        /**
         * make instance of class
         *
         * @return void
         */
        public function __construct()
        {
            $this->handler = new \GuzzleHttp\Client([
                 'base_uri' => 'your base api url',
            ]);
        }
        /**
         * handle GET requests
         *
         * @param string $endpoint
         * @return ResponseInterface
         */
        public function get(string $endpoint)
        {
            return $this->handler->get($endpoint);
        }
        /**
         * handle POST requests
         *
         * @param string $endpoint
         * @param array $params
         * @return ResponseInterface
         */
        public function post(string $endpoint, array $params)
        {
            return $this->handler->post($endpoint, [
                'body' => $params
            ]);
        }
    }
    In the example above, you can only see get and post methods but in the complete solution, you should provide other HTTP verb handlers, you can see a simple example of using this class in other classes below.
    namespace App\Http\Controllers;
    
    use App\Handlers\Http\HttpHandler;
    
    class UsersController
    {
        /**
         * instnace of httpClient
         *
         * @var HttpHandler
         */
        private $httpClient;
    
        /**
         * make instnace of class
         *
         * @return void
         */
        public function __construct()
        {
            $this->httpClient = new HttpHandler();
        }
    
        /**
         * get all users
         *
         * @return void
         */
        public function getAllUsers()
        {
            $response = $this->httpClient->get('users');
        }
    }
    I hope this article could help you to get better insight from the Adapter/Wrapper pattern and you're willing to use it in your projects.

Tags

Comments

Topics of interest