Adapter mode + source code analysis

Posted by xoligy on Fri, 28 Jan 2022 02:44:57 +0100

Cases in life

Different countries use different plugs. One day, Lao Wang, a programmer, went to Thailand for a trip and was about to charge his mobile phone when he came back to the hotel at night. He found that the charger could not fit the socket in Thailand. At this time, Lao Wang was very depressed, so he called the management personnel of the Hotel. The management personnel of the hotel gave Lao Wang a multifunctional conversion plug (equivalent to an adapter), and Lao Wang's mobile phone can be charged, So Lao Wang can happily play with his mobile phone and visit CSDN happily.

Adapter mode

Concept: an Adapter Pattern serves as a bridge between two incompatible interfaces. This type of design pattern belongs to structural pattern, which combines the functions of two independent interfaces. The main purpose is compatibility, so that the two classes that cannot work together due to interface mismatch can work together.

Classification:

Class adapter mode, object adapter mode, interface adapter mode

working principle

  1. Adapter mode: convert the interface of one class to another Make the classes whose original interfaces are incompatible compatible
  2. From the user's point of view, there is no Adaptee, which is decoupled
  3. The user calls the target interface method transformed by the adapter, and then the adapter calls the relevant interface method of the Adaptee
  4. After receiving the feedback result, the user feels that he is only interacting with the target interface, as shown in the figure

Class diagram

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 the 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): 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.

Class adapter mode

Next, we use the form of code to solve the problem of inappropriate plug of Lao Wang charger.

//Target is equivalent to the type of charger socket Lao Wang wants
public interface Socket {
    void charge();
}
//Adaptee how can Lao Wang get the type of charger Lao Wang needs
public class TwoSocket {
    public void chargeTwo(){
        System.out.println("Lao Wang got his wish and got a double hole socket......");
");
    }
}
//Adapter 
public class TwoSocketAdapter extends TwoSocket implements Socket {
    @Override
    public void charge() {
      chargeTwo();
    }

}
public class Test {
    public static void main(String[] args) {
        Socket player=new TwoSocketAdapter();
        player.charge();
    }
}
Lao Wang got his wish and got a double hole socket......

object adapter

The difference between object adapter and class adapter is that class adapter adapts through inheritance and object adapter adapts through association. Here, you only need to modify TwoSocketAdapter slightly to convert it into an object adapter

public class TwoSocketAdapter implements Socket {
    private TwoSocketAdapter socketAdapter=new TwoSocketAdapter();
    @Override
    public void charge() {
     socketAdapter.chargeTwo();
    }

}

Interface adapter mode

  1. Some books are called: default adapter pattern or default adapter pattern.
  2. 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
  3. This applies when an interface does not want to use all its methods.

The following is a set of behavioral action interfaces. For example, an animal bird wants to realize its flying action. Interface adapter mode can be used. Design an abstract class to implement the interface. The subclass of the abstract class can selectively override some methods of the parent class to realize the requirements.

public interface Action {

    void play();
    void swim();
    void fly();
}
public abstract class FlyAction implements Action{
   
}
public class Bird{
    @Override
    public void fly() {
        System.out.println("fly.......");
    }
}

Adapter mode summary

Main advantages:

  1. Decouple the target class from the adapter class, and reuse the existing adapter class by introducing an adapter class without modifying the original structure.
  2. 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.
  3. The flexibility and expansibility 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".

Class adapter mode also has the following advantages:

  1. Since the adapter class is a subclass of the adapter class, some adapter methods can be replaced in the adapter class, making the adapter more flexible.

The object adapter mode also has the following advantages:

  1. An object adapter can adapt multiple different adapters to the same target;
  2. The subclass of an adapter can be adapted. Because the adapter and the adapter are related, the subclass of the adapter can also be adapted through the adapter according to the "Richter substitution principle".

The disadvantages of class adapter mode are as follows:

  1. For Java, C# and other languages that do not support multi class inheritance, only one adapter class can be adapted at a time, and multiple adapters cannot be adapted at the same time;
  2. The adapter class cannot be the final class. For example, it cannot be the final class in Java and the sealed class in C#;
  3. 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.

The disadvantages of the object adapter pattern are as follows:

  1. Compared with the class adapter pattern, some methods to replace the adapter class in the adapter are more troublesome. 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 scenarios:

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, and there is even 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.

Typical application of source code analysis adapter pattern

Adapter pattern in spring AOP

In Spring's Aop, Advice is used to enhance the function of the proxy class.

The types of Advice include: MethodBeforeAdvice, AfterReturningAdvice and ThrowsAdvice

There are corresponding interceptors in each type of Advice: MethodBeforeAdviceInterceptor, AfterReturningAdviceInterceptor, ThrowsAdviceInterceptor

Spring needs to encapsulate each Advice into a corresponding interceptor type and return it to the container, so it needs to use the adapter mode to convert the Advice
The three adapter classes Adaptee are as follows:

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable;
}

public interface AfterReturningAdvice extends AfterAdvice {
    void afterReturning(@Nullable Object var1, Method var2, Object[] var3, @Nullable Object var4) throws Throwable;
}

public interface ThrowsAdvice extends AfterAdvice {
}

The Target interface Target has two methods: one is to judge whether the Advice type matches, and the other is the factory method to create the interceptor corresponding to the Advice of the corresponding type

public interface AdvisorAdapter {
    boolean supportsAdvice(Advice var1);
    MethodInterceptor getInterceptor(Advisor var1);
}

The three Adapter classes are as follows. Note the corresponding relationship among Advice, Adapter and Interceptor

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}
}

@SuppressWarnings("serial")
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof AfterReturningAdvice);
	}
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
		return new AfterReturningAdviceInterceptor(advice);
	}
}

class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof ThrowsAdvice);
	}
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		return new ThrowsAdviceInterceptor(advisor.getAdvice());
	}
}

Client defaultadvisor AdapterRegistry

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
    private final List<AdvisorAdapter> adapters = new ArrayList(3);

    public DefaultAdvisorAdapterRegistry() {
        // The adapter is registered here
        this.registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
        this.registerAdvisorAdapter(new AfterReturningAdviceAdapter());
        this.registerAdvisorAdapter(new ThrowsAdviceAdapter());
    }
    
    public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
        List<MethodInterceptor> interceptors = new ArrayList(3);
        Advice advice = advisor.getAdvice();
        if (advice instanceof MethodInterceptor) {
            interceptors.add((MethodInterceptor)advice);
        }

        Iterator var4 = this.adapters.iterator();

        while(var4.hasNext()) {
            AdvisorAdapter adapter = (AdvisorAdapter)var4.next();
            if (adapter.supportsAdvice(advice)) {   // The adapter method is called here
                interceptors.add(adapter.getInterceptor(advisor));  // The adapter method is called here
            }
        }

        if (interceptors.isEmpty()) {
            throw new UnknownAdviceTypeException(advisor.getAdvice());
        } else {
            return (MethodInterceptor[])interceptors.toArray(new MethodInterceptor[0]);
        }
    }
    // ... Omit
}    

Here we look at the while loop, take out the registered adapter one by one, call the supportsAdvice() method to determine the type of Advice, and then call getInterceptor() to create the corresponding type of interceptor.

This should belong to the object adapter mode. The keyword instanceof can be regarded as the Advice method, but the Advice object here is passed in from the outside, not the member attribute.

Adapter pattern in spring MVC

Let's first look at the execution process of spring MVC

As can be seen from the figure, the adapter mode 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, 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 extension and close to modification.

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

public interface HandlerAdapter {
    boolean supports(Object var1);

    ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    long getLastModified(HttpServletRequest var1, Object var2);
}

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.
The controller s provided by springmvc are:

The HandlerAdapter implementation classes provided in spring MVC are as follows

HttpRequestHandlerAdapter the adapter code is as follows

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    public HttpRequestHandlerAdapter() {
    }

    public boolean supports(Object handler) {
        return handler instanceof HttpRequestHandler;
    }

    @Nullable
    public ModelAndView handle(HttpServletRequest request,
     HttpServletResponse response, Object handler) throws Exception {
        ((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 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.

public class DispatcherServlet extends FrameworkServlet {
    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;

		// Determine handler for the current request.
		mappedHandler = getHandler(processedRequest);
			
		// Determines the matching adapter for the current request
		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.

Topics: Java Design Pattern