In life, there are often two “things” that do not match directly, and a transformation layer needs to be added in the middle to realize incompatible interfaces or specifications become common. For example, the various power adapters we use, and the SSD I replaced on my computer some time ago, I added a conversion class in the middle because the new SSD was not compatible with the old one.

The same problem exists in the world of programming software. We need to use some mature components, but the interface or method definition prevents us from using them directly without modifying the stable code. The cost and cost of redevelopment are very high, which can be used by adding a layer of adaptation layer to transform the relationship between components and new users, so as to achieve the purpose of reuse components. This is how the adaptor pattern is used in software design today, and is often seen in many open source frameworks.

Definition and characteristics of patterns

An Adapter pattern is defined as follows: It converts one interface into another that the consumer expects, so that classes that do not fit together can work together.

The adapter pattern is divided into two types: class structure pattern and object structure pattern. The former has a higher degree of coupling between classes and uses inheritance or implementation mode, while the latter uses association mode to decouple.

Another is the Default Adapter Pattern. When it is not necessary to implement all the methods of an interface, an abstract class may be used to implement empty methods, so that the concrete implementation of the subclass to override the methods in the abstract class, to achieve the implementation of custom methods. The default adapter pattern is a variation of the adapter pattern and is widely used. The default adapter pattern, such as WindowAdapter, KeyAdapter, and MouseAdapter, is widely used in java.awt. Event, the event handling package of the JDK class library.

Structure and implementation of patterns

The structure of the adapter pattern in Java allows you to define an adapter that implements the interfaces of the current system while inheriting components from an existing component library. The adaptation of existing components to the target interface is accomplished in the adapter.

1. Adapter pattern structure object

  • Target interface: An interface required by the current system. It can be an abstract class or interface
  • Adaptee class: An existing component or interface that plays the role of an Adaptee
  • Adapter class: It is a converter that converts an adapter interface into a target interface by inheriting or referencing an adapter’s object, allowing customers to access the adapter in the format of the target interface.

The structure diagram of the adapter pattern is as follows: 1. Class structure pattern (inheritance or implementation)

2. Object adaptation (reference)

3. Default mode

1. Class structure mode (inheritance or implementation) analog power adapter, 220V standard voltage, 5V voltage required for mobile phone charging.

  • Adapter (standard voltage)
public class Adaptee {
	/ / voltage
	private Integer voltage = 220;

	public Integer standardVoltage(a) {
		System.out.println("Applied standard voltage" +voltage + "V");
		returnvoltage; }}Copy the code
  • Target interface
public interface Target {
	/** ** required voltage *@return* /
   Integer needVoltage(a);
}
Copy the code
  • Adapter (inheritance adapter that implements the target interface)
public class Adapter extends Adaptee implements Target{

	@Override
	public Integer needVoltage(a) {
		// Standard voltage
		Integer standarVoltage =  standardVoltage();
		// Switch to 5V
		Integer mobileVoltage = standarVoltage / 44 ; 
		returnmobileVoltage; }}Copy the code
  • Using the
public class Client {

	public static void main(String[] args) {
		Target adapter = new Adapter();
		Integer voltage = adapter.needVoltage();
		System.out.println("Adaptive target voltage:"+voltage); }}Copy the code

Execution Result:

Adaptive standard voltage :220V adaptive target voltage: 5VCopy the code

Class structure Pattern Case class diagram:

2. Object adaptation

  • Adapter (standard voltage)
public class Adaptee {
	/ / voltage
	private Integer voltage = 220;

	public Integer standardVoltage(a) {
		System.out.println("Applied standard voltage" +voltage + "V");
		returnvoltage; }}Copy the code
  • Target interface
public interface Target {
	/** ** required voltage *@return* /
   Integer needVoltage(a);
}
Copy the code
  • Adapter (reference adapter)
public class Adapter implements Target{
	// Object to be adapted
	private Adaptee adaptee;
	
	public Adapter(Adaptee adaptee) {
		this.adaptee = adaptee;
	}

	@Override
	public Integer needVoltage(a) {
		// Standard voltage
		Integer standarVoltage =  adaptee.standardVoltage();
		// Switch to 5V
		Integer mobileVoltage = standarVoltage / 44 ; 
		returnmobileVoltage; }}Copy the code
  • Use the client
public class Client {

	public static void main(String[] args) {
		// Object to be adapted
		Adaptee adaptee = new Adaptee();

		// Target fit
		Target adapter = new Adapter(adaptee);
		Integer voltage = adapter.needVoltage();
		System.out.println("Adaptive target voltage:" + voltage + "V"); }}Copy the code

Execution Result:

Adaptive standard voltage 220V adaptive target voltage: 5VCopy the code

Object adaptation Case Class diagram:

3. Default mode

  • Target interface
public interface Target {
	public void method1(a);
	
	public void method2(a);

	public void method3(a);
}
Copy the code
  • Adapter (Abstract class, schema empty implementation)
public abstract class AbstractAdapter implements Target{
	/** * mode all methods are empty implementation, concrete implementation logic, by subclass to override. * /
	@Override
	public void method1(a) {}@Override
	public void method2(a) {}@Override
	public void method3(a) {}}Copy the code
  • Using the square Client, subclasses override methods that need to be used
public class Client {

	public static void main(String[] args) {
		AbstractAdapter abstractAdapter = new AbstractAdapter() {
			
			@Override
			public void method1(a) {
				System.out.println("this method m1"); }}; abstractAdapter.method1(); }}Copy the code

Execution Result:

 this method m1
Copy the code

Default schema case structure diagram:

Application scenarios of the adapter mode

The Adapter pattern (Adapter) typically applies to the following scenarios.

  • An old system has classes that meet the functional requirements of the new system, but their interfaces are inconsistent with those of the new system.
  • Use a component provided by a third party, but the interface definition of the component is different from the interface definition required by you.

Advantages and disadvantages of the adapter pattern

The advantages are as follows:

  • The client can transparently invoke the target interface through the adapter.
  • Existing classes are reused, and existing systems reuse existing adapter classes without modifying the original code.
  • Decoupling the target class from the adapter class solves the problem of interface inconsistency between the target class and the adapter class.
  • It complies with the on/off principle in many business scenarios.

Its disadvantages are:

  • The adapter writing process needs to be considered in the context of business scenarios and can increase system complexity.
  • Increase code reading difficulty, reduce code readability, excessive use of adapters will make the system code messy.

Use of adapter patterns in source code

1. Adapter in SpringMVC Where the adapter pattern is used in SpringMVC is in our call core distribution classDispatcherServletIn thedoDispatch()Methods; Let’s take a look at the data flow of a SpringMVC request:

The flow of this diagram should not be described in detail. The request processing flow of the SpringMVC framework is shown in the diagram. To get back to the topic of this article, how the adapter pattern is applied to this process, let’s take a look at HandlerMapping. HandlerMapping can be simply referred to as Controller, which maps specific Controller through the requested Url. Different Cotroller implementation methods, the corresponding Controller processing class is also different, such as the @controller annotation way, or Controller implementation way; And how the HandlerAdapter knows from the Hander which processor we’re calling. First look at the HandlerAdapter interface

public interface HandlerAdapter {
    boolean supports(Object var1);

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

    long getLastModified(HttpServletRequest var1, Object var2);
}
Copy the code

His implementation class and subclass structure diagram:

The adapters that are implemented in different ways determine the adaptors that are implemented in different waysControllerFor common annotations, the corresponding adapter isRequestMappingHandlerAdapterInheriting abstractAbstractHandlerMethodAdapter.HandlerAdapterIn an interface, two methods are importantsupports(object o), to determine whether the adapter satisfies the current request,handle(HttpServletRequest var1, HttpServletResponse var2, Object var3), the callControllerMethod, returnModelAndViewObject. Concrete structure class diagram:

Back in the trunk, take a look at the doDispatch() method of the DispatcherServlet class. Space limit, cut some code

try {
                    processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest ! = request;// Based on the request, get the specific mapperHadler object, get the Controller that will handle the current request, also called Handler, so instead of returning Controller directly, Instead, it returns the HandlerExecutionChain request processing chain object that encapsulates Handler and Inteceptor
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
  // Get the specific adapter object, according to MappedHandler
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }

                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return; }}if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
                    }
// Call the controller method through the adapter and return ModelAndView
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    //...
Copy the code

GetHandlerAdapter () gets the adapter

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters ! =null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					returnadapter; }}}/ / a little
	}
Copy the code

As you can see from the source code, the process of obtaining an adapter is to go through all the adapters in the system and find the specified one with the SUPPORTS method. And when the adapter was registered. HandlerAdapters are actually a List of adapters. Register through the initialization method

	/** List of HandlerAdapters used by this servlet. */
	@Nullable
	private List<HandlerAdapter> handlerAdapters;
   
InitHandlerAdapters ()
protected void initStrategies(ApplicationContext context) {
		// Multifile upload component
		initMultipartResolver(context);
		// Initialize the locale
		initLocaleResolver(context);
		// Initialize the template handler
		initThemeResolver(context);
		// Initialize HandlerMapping
		initHandlerMappings(context);
		// Initialize the parameter adapter
		initHandlerAdapters(context);
		// Initialize the exception interceptor
		initHandlerExceptionResolvers(context);
		// Initialize the view preprocessor
		initRequestToViewNameTranslator(context);
		// Initialize the view converter
		initViewResolvers(context);
		// Initialize the FlashMap manager
		initFlashMapManager(context);
	}    
Copy the code

InitHandlerAdapters (Context) gets the adapter objects from the container. Through source code analysis of the use of adapters in SpringMVC, HandlerAdapter interface for our target interface, different implementation classes for the specific role of adapter, and different request types of Controller for the specific role of adapter, HandlerMapping is generally considered as a Controller role, of course, Controller to the implementation of HandlerMapping subclass, how to achieve through annotations or inheritance is not described here, specific details check the source code.