Design pattern --- adapter pattern

Adapter concept introduction

1. Sockets in different countries are different. If we travel abroad, we need to bring foreign plug converters to be compatible with foreign sockets;

2. The earphone hole of the mobile phone has round head and flat head. If the flat head earphone hole wants to connect to the round head earphone, an earphone converter is required;


The above-mentioned converter is actually an adapter; It is used for compatibility;

introduce

Adapter pattern: convert an interface into another interface desired by the customer, so that those classes with incompatible interfaces can work together. Its alias is wrapper. The adapter pattern can be either a class structured pattern or an object structured pattern.

In the adapter mode, we solve the problem of interface incompatibility by adding a new adapter class, so that classes that have no relationship can work together.

According to the different relationship between adapter class and adapter class, adapter mode can be divided into object adapter and class adapter. In object adapter mode, there is an association (aggregation) relationship between adapter and adapter; In the class adapter pattern, there is an inheritance (or implementation) relationship between the adapter and the adapter.

role

Target abstract class: the target abstract class defines the interface required by the customer, which can be an abstract class or interface, or a concrete class.

Adapter (adapter class): the adapter can call another interface as a converter to adapt Adaptee and Target. The adapter class is the core of the adapter pattern. In the object adapter, it connects the two by inheriting the Target and associating an Adaptee object.

Adaptee (adapter class - adapter interface): the adapter is the role to be adapted. It defines an existing interface that needs to be adapted. The adapter class is generally a specific class that contains the business methods that customers want to use. In some cases, there may be no source code of the adapter class.

Default adapter pattern: when you do not need to implement all the methods provided by an interface, you can first design an abstract class to implement the interface, A default implementation (empty method) is provided for each method in the interface. Then the subclass of the abstract class can selectively override some methods of the parent class to implement the requirements. It is applicable to the case where you do not want to use all methods in an interface, which is also called the single interface adapter model.

working principle

  • Adapter mode: convert the interface of one class to another, so that classes with incompatible interfaces can be compatible;
  • The Adaptee cannot be seen from the user's point of view;
  • The user calls the target interface method transformed by the adapter, and the adapter calls the relevant interface method of the Adaptee;

3 adapter modes

  • Class adapter mode
  • Object Adapter Pattern
  • Interface adapter mode

Class adapter mode demo

Taking the charger in life as an example, the charger itself is equivalent to the adapter, and 220V AC is equivalent to the Adaptee. Our goal is to convert 220V AC into 5V DC

The class to be adapted, that is, it is necessary to convert 220v voltage into 5v voltage

//The voltage in China is 220V
public class ChinaPower
{
    private final Integer outPut=220;
    public Integer getOutPut() {
        return outPut;
    }
}

The adapter interface is only responsible for defining the business logic methods required for transformation, and the specific implementation is completed by the adapter class

//Convert voltage to 5v --- adapter
public interface TransTo5V
{
    Integer transTo5V();
}

The adapter class inherits ChinaPower and implements the adapter interface, which is responsible for the specific business logic code implementation of converting 220v voltage into 5v

//Adapter class --- implement the adapter interface
public class ChinaAdapter extends ChinaPower implements TransTo5V
{
    //Convert 220v voltage to 5v
    @Override
    public Integer transTo5V()
    {
        //Get the adapted class, that is, we need to convert 220v voltage into 5v return voltage
        Integer output=super.getOutPut();
        //Perform voltage conversion operation
        return output/44;
    }
}

The Phone class requires an adapter for compatibility so that it can be charged

//The phone needs 5v to charge
public class Phone
{
    //Obtain a voltage of 5v through the adapter
    public void charging(ChinaAdapter chinaAdapter)
    {
        if(5==chinaAdapter.transTo5V())
        {
            System.out.println("Get 5 v,Charging...");
        }
        else
        {
            System.out.println("The voltage is too high and the pressure of the mobile phone is too high");
        }

    }
}

Charging test

public class test
{
    @Test
    public void test()
    {
        Phone p=new Phone();
        p.charging(new ChinaAdapter());
    }
}

Object Adapter Pattern

Take the above example as an example. This time, the adapter class no longer inherits ChinaPower, but replaces inheritance in the form of aggregation, which conforms to the "composite Reuse Principle" in the design pattern; java is a single inheritance mechanism, which can preserve the inheritance right of objects;

We only need to modify the adapter class:

//Adapter class --- implement the adapter interface
public class ChinaAdapter implements TransTo5V
{
    private ChinaPower chinaPower;
    //The assignment is completed through the constructor
    public ChinaAdapter(ChinaPower chinaPower)
    {
     this.chinaPower=chinaPower;   
    }
    //Convert 220v voltage to 5v
    @Override
    public Integer transTo5V()
    {
        //Get the adapted class, that is, we need to convert 220v voltage into 5v return voltage
        Integer output=chinaPower.getOutPut();
        //Perform voltage conversion operation
        return output/44;
    }
}

test

public class test
{
    @Test
    public void test()
    {
        Phone p=new Phone();
        p.charging(new ChinaAdapter(new ChinaPower()));
    }
}

Advantages of object adapters

Object adapter and class adapter are actually the same idea, but they are implemented in different ways. According to the principle of composition reuse, composition is used to replace inheritance, so it solves the limitation that class adapters must inherit the Adaptee;

Interface adapter mode

  • Default adapter pattern, also known as default adapter pattern;
  • Core idea: 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. The subclass of the abstract class can selectively override some methods of the parent class to implement the requirements;
  • It is applicable when an interface does not want to use all its methods;

Define an adapter interface:

public interface InterfaceTest {
	public void m1();
	public void m2();
	public void m3();
	public void m4();
}

The abstract class AbsAdapter implements the InterfaceTest method by default. When a subclass needs to use a method in the adapter interface instead of all the methods, it can rewrite the specific methods to be used by inheriting the abstract class without implementing all the methods in the adapter interface

public abstract class AbsAdapter implements InterfaceTest {

	//Default implementation
	public void m1() {}
	public void m2() {}
	public void m3() {}
	public void m4() {}
}

The Client calls the interface and rewrites the adapter abstract class method

public class Client {
	public static void main(String[] args) {
		
		AbsAdapter absAdapter = new AbsAdapter() {
			//We just need to override. We need to use interface methods
			@Override
			public void m1() {
				System.out.println("Used m1 Method of");
			}
		};
		
		absAdapter.m1();
	}
}

Comprehensive small case - using class adapter pattern

power – voltage with conversion

A top-level interface

public interface Power
{
     Integer getOutPut();
}

Branch 1: 220v voltage in China

//The voltage in China is 220V
public class ChinaPower implements Power
{
    private final Integer outPut=220;
    @Override
    public Integer getOutPut() {
        return outPut;
    }
}

Branch 2: 110v voltage in Japan

//Japan voltage 110v
public class JapenPower implements Power
{
    private final Integer output=110;
    @Override
    public Integer getOutPut() {
        return output;
    }
}

Adapter – adapter

Mismatch interface – DC5Adapter

//adapter interface
public interface DC5Adapter
{
    boolean support(Power power);
    Integer transTo5V(Power power);
}

Adapter class - ChinaAdapter - is only responsible for converting 220v voltage

//The adapter is responsible for converting 220v voltage in China into 5v voltage
public class ChinaAdapter implements DC5Adapter
{
    //At present, the adapter is only responsible for the function of converting 220v voltage into 5v
    private  static Integer voltage=220;
    //Judge whether the current adapter is competent for the conversion of incoming power voltage
    @Override
    public boolean support(Power power)
    {
      if(power.getOutPut().equals(voltage))
      return true;
      return false;
    }
    //Convert 220v voltage to 5v
    @Override
    public Integer transTo5V(Power power)
    {
        //Get the adapted class, that is, we need to convert 220v voltage into 5v return voltage
        Integer output=power.getOutPut();
        //Perform voltage conversion operation
        return output/44;
    }
}

Adapter class - JapenAdapter - is only responsible for converting 110v voltage

//The adapter is responsible for converting 110v voltage in Japan into 5v voltage
public class JapenAdapter implements DC5Adapter
{
    //At present, the adapter is only responsible for the function of converting 110v voltage into 5v
    private  static Integer voltage=110;
    //Judge whether the operation of converting Japanese 110v voltage to 5v voltage is supported
    @Override
    public boolean support(Power power) {
        if(power.getOutPut()==voltage)
            return true;
        return false;
    }
    //Converting 110v voltage to 5v
    @Override
    public Integer transTo5V(Power power)
    {
        //Obtain the adapted class, that is, we need to convert 110v voltage into 5v return voltage
        Integer output=power.getOutPut();
        //Perform voltage conversion operation
        return output/22;
    }
}

FindAdapter – find the right adapter

//The phone needs 5v to charge
public class FindAdapter
{
    //A set that holds all adapters
    private static final Set<DC5Adapter> DC5Adapters=new HashSet<>();
    //Initialization through static code blocks
    static
    {
        DC5Adapters.add(new ChinaAdapter());
        DC5Adapters.add(new JapenAdapter());
    }
    // Find the appropriate transformer according to the voltage
    public DC5Adapter getPowerAdapter(Power power)
    {
        DC5Adapter dc5Adapter=null;
        for(DC5Adapter da:DC5Adapters)
        {
            //If the traversal reaches the transformer with the appropriate current voltage, it will exit the traversal directly
            if(da.support(power))
            {
                dc5Adapter=da;
                break;
            }
        }
        //If no suitable transformer is found after traversing all the transformers, an exception is thrown
        if(dc5Adapter==null)
        {
            throw  new IllegalArgumentException("A suitable transformer could not be found");
        }
        //Return to the appropriate transformer found
        return dc5Adapter;
    }
}

test

public class test
{
    @Test
    public void test()
    {
        //Finding the right transformer is the first step
        FindAdapter fa=new FindAdapter();
        //Look for a transformer that can convert 220v to 5v, that is, an adapter
        DC5Adapter adapter = fa.getPowerAdapter(new ChinaPower());
        //Output the voltage after the conversion of the current transformer
        System.out.println(adapter.transTo5V(new ChinaPower()));
    }
}

Adapter mode summary

Main advantages

  • Decouple the target class from the adapter class, and reuse the existing adapter class by introducing an adapter class without modifying the original structure.
  • It increases the transparency and reusability of the class, encapsulates the specific business implementation process in the adapter class, which is transparent to the client class, and improves the reusability of the adapter. The same adapter class can be reused in multiple different systems.
  • The flexibility and scalability are very good. By using the configuration file, you can easily replace the adapter, or add a new adapter class without modifying the original code, which fully conforms to the "opening and closing principle".

Specifically, the class adapter pattern has the following advantages:

  • Since the adapter class is a subclass of the adapter class (adapter interface or abstract class implemented by adapter interface), the methods of some adapters (adapter interface or abstract class implemented by adapter interface) can be replaced in the adapter class, making the adapter more flexible.
  • An object adapter can adapt multiple different adapters (adapter interface or abstract class implemented by adapter interface) to the same target;
  • A subclass of an adapter can be adapted. Because the adapter and the adapter (adapter interface or abstract class implemented by adapter interface) are associated, according to the "Richter substitution principle", the subclass of the adapter (adapter interface or abstract class implemented by adapter interface) can also be adapted through the adapter.

Main disadvantages

  • For Java, C# and other languages that do not support multi class inheritance, only one adapter class (adapter interface or abstract class implemented by adapter interface) can be adapted at a time, and multiple adapters cannot be adapted at the same time;
  • The adapter class cannot be the final class. For example, it cannot be the final class in Java and the sealed class in C#;
  • In Java, C# and other languages, the target abstract class in the class adapter pattern can only be an interface, not a class, and its use has certain limitations.
  • Compared with the class adapter pattern, some methods to replace the adapter class in the adapter are more cumbersome. If you must replace one or more methods of the adapter class, you can first make a subclass of the adapter class, replace the methods of the adapter class, and then adapt the subclass of the adapter class as a real adapter. The implementation process is more complex.

Applicable scenario

  • The system needs to use some existing classes, and the interfaces of these classes (such as method names) do not meet the needs of the system, or even have no source code of these classes.
  • I want to create a reusable class to work with some classes that are not closely related to each other, including some classes that may be introduced in the future.

Adapter pattern in spring MVC

Spring MVC processing request flow

  • Step 1: the user sends a request to the front-end controller DispatcherServlet;
  • In steps 2 and 3, the dispatcher servlet receives the request and calls the handler mapping processor mapper to find the specific processor according to the request url; The generated processor object and the processor interceptor (generated if any) are returned to the dispatcher servlet;
  • Step 4 DispatcherServlet calls the specific processor through the handler adapter processor adapter; (adapter mode is used in this step)
  • Step 5 and 6 execute the processor (Controller, also known as back-end Controller) and return to ModelAndView;
  • Step 7: the HandlerAdapter returns the controller execution result ModelAndView to the dispatcher servlet
  • In step 8, the dispatcher servlet passes the ModelAndView to the viewResolver view parser (but if the @ responsebody annotation is added, the returned value will not pass through the viewResolver, but directly return the object);
  • Step 9: the viewsolver returns the specific View after parsing;
  • Step 10: the dispatcher servlet renders the View (that is, fills the View with model data);
  • Step 11 the dispatcher servlet responds to the user.

The HandlerAdapter in spring MVM (step 4 above) uses the adapter mode;

Research on the source code of adapter mode in request processing method

The adapter pattern in Spring MVC is mainly used to execute the request processing method in the target Controller.

In Spring MVC, DispatcherServlet is the user, HandlerAdapter is the desired interface (adapter interface), the specific adapter implementation class is used to adapt the target class, and Controller is the class to be adapted.

Why use the adapter pattern in Spring MVC? There are many kinds of controllers in Spring MVC. Different types of controllers process requests through different methods. If the adapter mode is not used, the DispatcherServlet directly obtains the Controller of the corresponding type, which needs to be judged by itself, like the following code:

if(mappedHandler.getHandler() instanceof MultiActionController){  
   ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){  
    ...  
}else if(...){  
   ...  
}  

In this way, suppose that if we add a HardController, we need to add a line of if(mappedHandler.getHandler() instanceof HardController) to the code. This form makes the program difficult to maintain and violates the opening and closing principle in the design pattern - open to extensions and close to modifications.

Let's take a look at the source code. The first is the adapter interface HandlerAdapter

//adapter interface
public interface HandlerAdapter 
{
    //Judge whether the current controller request can be processed by the current adapter class
    boolean supports(Object var1);
      
      //The following request processing method will be executed and a ModelAndView object will be returned only after the current request processing is supported
     ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    long getLastModified(HttpServletRequest var1, Object var2);
}

At present, each Controller of the interface has an adapter corresponding to it. In this way, each custom Controller needs to define an adapter that implements HandlerAdapter.

The Controller implementation classes provided in springmvc are as follows:

The HandlerAdapter implementation classes provided in spring MVC are as follows

HttpRequestHandlerAdapter the adapter code is as follows:

//Different adapter classes implement different functions
//The current HttpRequestHandlerAdapter adapter class is only responsible for processing requests related to HttpRequest
public class HttpRequestHandlerAdapter implements HandlerAdapter {
    public HttpRequestHandlerAdapter() {
    }
    
    //Judge whether the current controller request is of type HttpRequestHandler
    //The current adapter only supports handling handler s of the current type 
    public boolean supports(Object handler) {
        return handler instanceof HttpRequestHandler;
    }

    //If the verification is supported, the following method will be called for specific logical processing
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //First, cast the type to the specified handler type, and then call the method of this type to process the corresponding request
    //Call the handleRequest of HttpRequestHandler to process the corresponding request
        ((HttpRequestHandler)handler).handleRequest(request, response);
        return null;
    }

    public long getLastModified(HttpServletRequest request, Object handler) {
        return handler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L;
    }
}

After 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 handler type and return the adapter object to the user. Then, the method used to process the request in the Controller can be called uniformly through the handle () method of the adapter.

public class DispatcherServlet extends FrameworkServlet {
//The list collection used to store all HandlerAdapter adapter classes
    private List<HandlerAdapter> handlerAdapters;
    
    //Initialize handlerAdapters
    private void initHandlerAdapters(ApplicationContext context) {
        //.. Omit
    }
    
    // Traverse all HandlerAdapters and find the matching adapter through supports
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				return ha;
			}
		}
	}
	
	// Distribute the request. The request needs to find a matching adapter to process
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;

		//Find the handler that can handle the current processedRequest, that is, the request
		mappedHandler = getHandler(processedRequest);
			
		// Determines the adapter class that the current handler matches
		HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

		ha.getLastModified(request, mappedHandler.getHandler());
					
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    }
	// ... Omit
}	

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.

summary


Reason analysis for using HandlerAdapter:

If the processor types are different and there are multiple implementation methods, the calling method is not determined. If you directly call the Controller method, you have to constantly use if else to judge which sub class it is and then execute it. If you want to extend the Controller later, you have to modify the original code, which violates the OCP principle;

explain:

  • Spring defines an adaptation interface so that each Controller has a corresponding adapter implementation class;
  • The adapter executes the corresponding method instead of the controller;
  • When extending the Controller, you only need to add an adapter class to complete the extension of spring MVC;

Reference articles

Design pattern 8 - adapter pattern and spring MVC source code analysis
Design pattern | adapter pattern and typical application
Adapter pattern (spring MVC source code analysis)
Design pattern | adapter pattern and typical application

Keywords: Java C#

Added by jmantra on Sat, 18 Dec 2021 16:59:31 +0200