Adapter concept

Adapter Pattern: The translation of one interface into another that the customer wants, so that classes that are incompatible with the interface can work together. The alias is wrapper. The adapter pattern can be used as either a class schema or an object schema. In the adapter pattern, we address interface incompatibilities by adding a new adapter class so that otherwise unrelated classes can work together.

Adapter patterns can be divided into object adapters and class adapters

  • Object adapter pattern

The relationship between adapters and adaptors is associative or associative

  • Adapter-like pattern

There is an inheritance (or implementation) relationship between an adapter and an adapter

Adapter features

The adapter pattern has three main roles:

  1. Target abstract class

The target abstract class defines the interface required by the customer. It can be an abstract class or interface or a concrete class.

  1. The adapter class

An allocator can call another interface, acting as a converter, to match the adapter and the target abstract class. The adapter class is the core of the adapter pattern. In the object adapter, it connects the two by inheriting the target abstract class and associating an adapter object.

  1. Matches class

An adapter is a role to be adapted. It defines an existing interface that needs to be adapted. An adapter class is usually a concrete class that contains the business methods that the customer wants to use.

What problem does the adapter mainly solve

The adapter pattern was born to solve the problem of incompatibilities between objects that need to cooperate. An adapter is used to bridge the cooperation between two incompatible objects, and the two incompatible objects cooperate through the adapter without direct contact. For example, there are the following questions:

  • The system needs to use an existing class that does not meet the system’s needs, that is, the interface is incompatible.
  • You need to build a reusable class to work with classes that don’t have much to do with each other.
  • A unified output interface is required, but the type of output is unpredictable.

Common usage scenarios

  • The usage scenario of the adapter pattern is generally considered when we need to modify some running code and want to reuse the existing code for new functions.
  • In the Spring framework, Adapter patterns are used extensively. Readers can open their IDE and try to search globally with the keyword “Adapter”, which will definitely have many practical applications.

This may be a little confusing to you, but we'll do it in code later

Oo Design Principles

  1. Single responsibility principle: An object should contain only a single responsibility, and that responsibility is fully encapsulated in a class. It is the guiding basis of high cohesion and low coupling theory.

  2. Open closed principle: the principle that software entities should be extended without modifying the original code.

  3. Interface isolation principle: Control the granularity of interfaces. If the interfaces are too small, the system will be flooded with interfaces, which is not conducive to maintenance. The interface size should not be too large. Too large interfaces violate the interface isolation principle, resulting in poor flexibility and inconvenient use.

  4. Richter’s substitution principle: A parent class should be designed as an abstract class or interface, with subclasses inheriting or implementing the parent class interface and implementing methods declared in the parent class.

  5. The dependency inversion principle: Program for interfaces, not implementations.

  6. Principle of composite reuse: Use object composition in preference to inheritance for reuse purposes.

The adaptor pattern is a good embodiment of the open and closed principle. In addition, the adaptor pattern should use composition as much as possible, that is, the object adaptor pattern, and less inheritance or implementation, that is, the specific requirements of the composite reuse principle.

The code example

Example 1

  1. Let’s define an interface like the Service interface that we always use in Web development, and write some random method. Let’s just write one:
interface Param1{
    void doingThings(a);
}
Copy the code
  1. Define an implementation class for the interface above
class Param1Impl implements Param1{
    @Override
    public void doingThings(a) {
        System.out.println("I'm working."); }}Copy the code
  1. Let’s define a method whose input parameter is an object of interface type to call the corresponding implementation class method.
public static void work(Param1 param1){
    System.out.println("Ready to get to work.");
    param1.doingThings();
    System.out.println("It's done.");
}
Copy the code
  1. After the above three steps are defined, we can test them:
public static void main(String[] args) {
    Param1 param1=new Param1Impl();
    work(param1);
}
Copy the code

Output: Ready to get to work I’m just getting to work

Ok, so that’s our most common code. If this code is history code, now all of a sudden you have a new requirement, you have a new interface Param2 and the corresponding implementation Param2Impl and these two classes as a new logic want to reuse the old history code logic. You want to reuse the work method in the code above. Examples of interface code for Param2:

interface Param2{
    void doingThings(a);
}

class Param2Impl implements Param2{

    @Override
    public void doingThings(a) {
        System.out.println("Param2 is working"); }}Copy the code

The problem with the new process based on the above is that the parameter type received by the above work method is Param1, so the corresponding type for the new process is Param2, so it seems impossible to reuse the previous work method at all. At this point, an intermediate layer of transformation is needed to finally make the Work method compatible with the new logic. We need to add an adapter class:

class Adapter implements Param1{

    private Param2 param2;

    public Adapter(Param2 param2) {
        this.param2 = param2;
    }

    @Override
    public void doingThings(a) { param2.doingThings(); }}Copy the code

For the above adapter class to fit the original work method, the adapter would have to be disguised as the input parameter type of the original logic, which is Param1 in our example. We are simulating a call:

Param2 param2=new Param2Impl();
Adapter adapter = new Adapter(param2);
work(adapter);
Copy the code

The results of

Ready to work Param2 Working Finished work

This satisfies our requirement of reusing old methods with new logic.

Example 2

We define an interface for animal behavior

interface Animal{
    public void run(a);
    public void sing(a);
    public void swim(a);
}
Copy the code

We then implement this interface with a class

class Dog implements Animal{

    @Override
    public void run(a) {}@Override
    public void sing(a) {}@Override
    public void swim(a) {}}Copy the code

This class does not want to implement the sing() method in the interface, but it must always implement abstract methods when implementing the interface. In this case we need an adapter class to switch to.

abstract class Afunc implements Animal{
    @Override
    public void run(a) {}@Override
    public void sing(a) {}@Override
    public void swim(a) {}}Copy the code

This is an adapter that implements the interface and nulls the methods in the interface. Next we let the class inherit the adapter and implement methods that override their own needs.

class bird extends Afunc{
    @Override
    public void sing(a) {
        super.sing(); }}Copy the code

In fact, if you take a real life example, let’s say you’re developing a piece of real estate, the interface is like a sketch, it determines what the thing you’re building will look like, what it will look like, how tall it will be. The abstract class that implements the interface is a half-finished product, and you’ve already done some of the work, like the interior space design, the house design, which determines whether you’re building a mall or a residential area or something else. But this degree certainly cannot be taken to use, whether residential or shopping malls, each householder has their own preferences, which needs to be decorated, decorated into their favorite appearance, you can use, this is the final concrete implementation of the class.

That's what we need.

An embodiment of the adapter pattern

There are also many applications of the adapter pattern in many frameworks or JDK. Here we briefly introduce it:

InputStreamReader

Is a byte to character bridge that causes the specified charset to read bytes and decode them into characters. (Decoding: to convert an unreadable byte into a readable character)

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
Copy the code

We often see code like this. System. In is actually of type InputStream, and since BufferedReader and InputStream don’t work together, we introduce BufferedReader as an adapter class. Convert the interface of the InputStreamReader class to an interface available to the BufferedReader class.

The adapter pattern in Spring MVC

We wrote a lot of controllers when we were developing with SpringMVC, and different types of controllers handle requests in different ways. If the adapter pattern is not used, the DispatcherServlet directly fetches the corresponding type of Controller and is left to its own discretion. There might be a lot of if-else, such as:

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

And if we do that then every time we write a controller we have to put an else-if down here. It also violates the open and closed principle of design pattern-open for extension, closed for modification. Let’s simulate for ourselves how Spring implements the mapping controller via HandlerAdapter. Some other logic interferes with the main flow due to directly pasting Spring code. Let’s take the core process out for ourselves:

  1. First we have a Controller interface
public interface Controller {}Copy the code
  1. Then there are several concrete implementations of specific controllers
public class HttpController implements Controller{  
    public void doHttpHandler(a){  
        System.out.println("http..."); }}public class SimpleController implements Controller{  
    public void doSimplerHandler(a){  
        System.out.println("simple..."); }}public class AnnotationController implements Controller{  
    public void doAnnotationHandler(a){  
        System.out.println("annotation..."); }}Copy the code

In fact, this is modeled after the Controller implementation in Spring, which provides the following implementation:

If your code only writes here, you’re going to have to write an if-else every time you add a controller that’s going to be handled by that controller implementation class.

Then we use the addition of adapter class to solve this problem: 3. We define an Adaper interface

public interface HandlerAdapter {  
    public boolean supports(Object handler);  
    public void handle(Object handler);  
}  
Copy the code
  1. Writing adapters
public class SimpleHandlerAdapter implements HandlerAdapter {  
    public void handle(Object handler) {  
        ((SimpleController)handler).doSimplerHandler();  
    }  
  
    public boolean supports(Object handler) {  
        return (handler instanceofSimpleController); }}public class HttpHandlerAdapter implements HandlerAdapter {  
  
    public void handle(Object handler) {  
        ((HttpController)handler).doHttpHandler();  
    }  
  
    public boolean supports(Object handler) {  
        return (handler instanceofHttpController); }}public class AnnotationHandlerAdapter implements HandlerAdapter {  
  
    public void handle(Object handler) {  
        ((AnnotationController)handler).doAnnotationHandler();  
    }  
  
    public boolean supports(Object handler) {  
          
        return (handler instanceofAnnotationController); }}Copy the code

The HandlerAdapter implementation class provided in SpringMVC is as follows5. Finally we simulate a DispatcherServlet

public class DispatchServlet {  
      
    public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();   
      
    public DispatchServlet(a){  
        handlerAdapters.add(new AnnotationHandlerAdapter());  
        handlerAdapters.add(new HttpHandlerAdapter());  
        handlerAdapters.add(new SimpleHandlerAdapter());  
    }  
      
      
    public void doDispatch(a){  
          
        // SpringMVC fetches the handler object from request.
        // No matter what Controller is implemented, the adapter can always be adapted to get the desired result
        // Simulate a specific controller
        SimpleController controller = new SimpleController();  
        // Get the corresponding adapter
        HandlerAdapter adapter = getHandler(controller);  
        // Execute the corresponding controller corresponding method through the adapter
        adapter.handle(controller);  
          
    }  
      
    public HandlerAdapter getHandler(Controller controller){  
        for(HandlerAdapter adapter: this.handlerAdapters){  
            if(adapter.supports(controller)){  
                returnadapter; }}return null;  
    }  
      
    public static void main(String[] args){  
        newDispatchServlet().doDispatch(); }}Copy the code

This is how the adapter pattern is applied throughout the core flow of springMVC.

Application in actual project scenarios

  • First, background: we belong to the center service. The intermediate platform needs to make a unified package of various types of services of each business line, and then provide interfaces for external use. This is very common in our normal development. A system can receive a variety of MQ messages or interfaces, which can be very expensive to develop one by one and difficult to expand later. At this point, you want a system that can be configured to access external MQ, and the added MQ will continue to be used just as the existing logic.
  • Implementation approach: MQ for new access lines of business to use historical logic requires an adapter for the new lines of business in adaptation of historical business attribute, so that the downstream accept MQ consumers modification cost will be greatly reduced, for instance in the adapter will be new lines of business in A mapped to historical MQ corresponding attribute A1.

The specific code will not be posted. This is just an example of a practical application scenario.