Adapter Pattern

Posted by thomasd1 on Tue, 08 Mar 2022 07:39:09 +0100

1, Definition

Adapter Pattern: one of the structural patterns, which converts the interface of a class into another interface desired by the customer. The Adapter Pattern allows classes that cannot work together because of incompatible interfaces to work together.

2, UML class diagram

3, Roles and responsibilities

  • Target role: this role defines what kind of interface to convert other classes, that is, our expected interface.
  • Source role (Adaptee): the "who" you want to convert into the target role is the source role. It is an existing and well functioning class or object.
  • Adapter role: the core role of the adapter pattern. The other two roles are existing roles, and the adapter role needs to be newly established. Its responsibility is very simple: convert the source role into the target role by inheritance or class association.

4, Code implementation

Take chestnuts for example. I bought a ticket today and flew to Hong Kong Disneyland. In the evening, I returned to the hotel to charge my laptop, but at this time, I found that the socket in Hong Kong is a British triangular socket, and my charger can't plug in. At this time, we can use the adapter mode for adaptation.

  • Class adapter: the adapter is implemented through classes. By inheriting and implementing the interface through classes, the adapter obtains the information of the adapted class, converts and rewrites the output to the adapted interface.

    Chinese socket (Target character)

    @AllArgsConstructor
    @Data
    public class ChineseStandard {
        public String getChineseStandard() {
            return "Chinese socket";
        }
    }
    

    British socket (source role Adaptee)

    public interface BritishStandard {
        String getBritishStandard();
    }
    

    Socket Adapter (Adapter role Adapter)

    public class StandardAdapter extends ChineseStandard implements BritishStandard {
        @Override
        public String getBritishStandard() {
            return this.getChineseStandard();
        }
    }
    

    Laptop (Client)

    public class Notebook {
        public void charge(BritishStandard britishStandard) {
            if ("Chinese socket".equals(britishStandard.getBritishStandard())) {
                System.out.println("Charging succeeded!");
            } else {
                System.out.println("Charging failed!");
            }
        }
    }
    

    Test class

    public class AdapterTest {
        public static void main(String[] args) {
            // Charging succeeded!
            new Notebook().charge(new StandardAdapter());
        }
    }
    
  • Object adapter: the adapter is implemented through the instance object (passed by the constructor), instead of inheritance. The rest are basically similar adapters.

    We can modify the socket adapter

    @AllArgsConstructor
    public class StandardAdapter implements BritishStandard {
        private ChineseStandard chineseStandard;
    
        @Override
        public String getBritishStandard() {
            return chineseStandard.getChineseStandard();
        }
    }
    

    Test class

    public class AdapterTest {
        public static void main(String[] args) {
            // Charging succeeded!
            new Notebook().charge(new StandardAdapter(new ChineseStandard()));
        }
    }
    

    If there are other methods we don't need in our source target interface and we don't want to implement it, we can take the adapter as an abstract class. When we implement the adapter abstract class, we just need to rewrite the methods we need. At this time, we use the interface adapter.

  • Interface adapter: when you do not need to implement all the methods provided by the interface, you can first design an abstract class to implement the interface and provide a default implementation (empty method) for each method in the interface. Then the subclass of the abstract class can selectively override some methods of the parent class to meet the requirements.

    British socket (source role Adaptee)

    public interface BritishStandard {
        String getBritishStandard();
    
        String getTypeC();
    
        String getUSB();
    }
    

    Socket Adapter (Adapter role Adapter)

    @AllArgsConstructor
    public abstract class StandardAdapter extends ChineseStandard implements BritishStandard {
    
        @Override
        public String getBritishStandard() {
            return null;
        }
    
        @Override
        public String getTypeC() {
            return null;
        }
    
        @Override
        public String getUSB() {
            return null;
        }
    }
    

    Test class

    public class AdapterTest {
        public static void main(String[] args) {
            StandardAdapter standardAdapter= new StandardAdapter() {
                @Override
                public String getBritishStandard() {
                    return new ChineseStandard().getChineseStandard();
                }
            };
            // Charging succeeded!
            new Notebook().charge(standardAdapter);
        }
    }
    

5, Source code analysis

Let's first look at the working principle of Spring MVC

  1. The browser sends a request to the controller (dispatcher servlet)
  2. The controller looks for the corresponding Handler from the Handler mapping according to the request address
  3. HanldlerMapping returns the found Handler
  4. DispatcherServlet finds the corresponding HandlerAdaptor according to the found Handler
  5. Execute the corresponding Handler method
  6. The Handler encapsulates the execution result and the view name to respond into a ModelAndView object
  7. The controller finds the corresponding ViewResolver according to the returned ViewName (View resolution). The ViewResolver renders the Model into the View
  8. Returns the rendering results to the controller
  9. Finally, the result will be responded to the client browser

It can be seen that the adaptation in Spring MVC mainly executes the request processing method of the Controller. In Spring MVC, DispatcherServlet is the user, HandlerAdapter is the desired interface (Target role), and Controller is the source role (Adaptee). There are many kinds of controllers in Spring MVC. Different types of controllers process requests through different methods.
Let's first look at the HandlerAdapter interface

The controller provided by Spring MVC is as follows.

The adapters provided by Spring MVC are as follows.
Each Controller of the interface has an adapter corresponding to it. In this way, each customized Controller needs to define an adapter that implements HandlerAdapter.
We enter the DispatcherServlet class, and if we get the name of the adapter when viewing.


When the Spring container is started, all defined adapter objects will be stored in a List collection. When a request comes, the DispatcherServlet will find the corresponding adapter through the type of handler and return the adapter object to the user. Then, the method used to process the request in the Controller can be called through the handle() method of the adapter. Through the adapter mode, we hand over all controllers to the HandlerAdapter for processing, which eliminates the need to write a large number of if else statements to judge the Controller, and is more conducive to expanding the new Controller type.

6, Analysis of advantages and disadvantages

Class Adapter
Advantages: the method of Adaptee class can be rewritten according to requirements, which enhances the flexibility of the Adapter.
Disadvantages: there are certain limitations. Because the class adapter needs to inherit the Target class, and Java is a single inheritance mechanism, it is required that the Adaptee class must be an interface.

object adapter
Advantages: the same Adapter can adapt the Adaptee class and its subclasses to the target interface.
Disadvantages: when you need to redefine the Adaptee behavior, you need to redefine the subclass of Adaptee and adapt the adapter combination.

Interface adapter
Advantages: the interface method can be rewritten flexibly and conveniently.
Disadvantages: because it is in the form of anonymous internal classes, it is not conducive to code reuse.

7, Applicable scenario

  • The system needs to reuse existing classes, and the interface of this class does not meet the requirements of the system. The adapter mode can be used to make those classes that cannot work together due to incompatible interfaces work together.
  • When multiple components have similar functions, but the interfaces are not unified and may be switched frequently, the adapter mode can be used so that the client can use them with a unified interface.

8, Summary

The adapter pattern transforms the existing interface into the interface expected by the customer class and realizes the reuse of the existing class. It is a design pattern with very high frequency and is widely used in software development. The adapter pattern is also used in open source frameworks such as Spring and driver design (such as database driver in JDBC).

Topics: Design Pattern