Preface – What do you get from reading this article

1. Understand the basic principles of object orientation, which are the foundation of design patterns

2. Understand the design patterns used in Retrofit

3. Understand the difference between decorator and proxy patterns

The main work Retrofit does

Retrofit is a wrapper around a RESTful HTTP Web request framework. The web request work is essentially done by OkHttp, while Retrofit is only responsible for the wrapper around the web request interface.

A generic Network request framework does what is shown in the figure above

1. Build Request (API parameter Configuration)

2. Executor (there are many variations, such as queued or not, in or out order, thread management)

3. Parse callback(parse data, return T to upper level)

What Retrofit does is divided into the following three parts:

1. Configure THE API parameters with annotations to stitch the Resultful API into a real request URL

2.CallAdapter(you can also call it executor), which specifies the actual network request

3.Converter(Parse the data and convert it to T)

Six principles of object orientation

  • Single Responsibility Principle — SRP

The single responsibility principle is well understood as a class that tries to do only one thing.

  • Open Close Principle — OCP

The open closed principle defines that objects (classes, modules, functions, etc.) in software should be open for extension but closed for modification. That is, when the requirements change, we need to modify the code, at this time we should try to extend the original code, rather than modify the original code, because this may cause more problems.

  • Liskov Substitution Principle — LSP

The Li substitution principle is simply stated: all references to a base class must be able to transparently use objects from its subclasses. Richter’s substitution principle is commonly put: a subclass can extend the functionality of its parent class, but cannot change the original functionality of the parent class

Richter’s substitution is similar to the open and closed principle, in that it is open for extension and closed for modification

  • Dependence Inversion Principle — DIP

The principle doesn’t mean anything in its name. A high-level module should not depend on a low-level module; both should depend on its abstraction; Abstraction should not depend on details; Details should depend on abstractions. Simply put, it is as much interface oriented programming as possible.

The principle of dependency inversion is expressed in Java language as follows: dependencies between modules occur through abstraction, while there are no direct dependencies between implementation classes. Their dependencies are generated through interfaces or abstract classes.

  • Interface Isolation Principle — ISP

The interface isolation principle is defined as: a client should not rely on interfaces it does not need; The dependency of one class on another should be based on the smallest interface. Minimal interfaces. Bloated interfaces can be divided into multiple interfaces based on their functions

These design ideas can be formed by using the first letter of the English alphabet. Programs that meet these five principles are also said to meet the SOLID criteria.

  • Law of Demeter — LOD

Demeter’s principle, also known as the principle of least knowledge, defines that one object should know the least about other objects. Because the closer the relationship between classes is, the greater the coupling degree is, and when one class changes, the greater the impact on the other class is. Therefore, this is also the general principle of software programming that we advocate: low coupling, high cohesion.

The relationship between six principles and design patterns

We use design patterns essentially for reuse and extension in subsequent development, which is probably the end goal

To use a metaphor, the six principles of object orientation are like mindsets to achieve this end, and twenty-three design patterns evolve from these mindsets.

Design patterns do not have to adhere to all six principles, but can vary according to the situation. Appearance mode, for example, violates the on/off principle.

Design patterns used in Retrofit

Constructor Pattern (built with Builder)

Retrofit retrofit =new Retrofit.Builder()
            .baseUrl(server1.url("/"))
            .build();
Copy the code

Advantages:

1. Good encapsulation, using the builder mode can make the client do not need to know the internal details of the product, more secure

2. Chain call, more concise, easy to understand

Appearance Mode (Facade mode)

It is required that communication between the outside and the inside of a subsystem must be carried out through a unified object. Facade mode provides a high-level interface that makes subsystems easier to use.

Retrofit doesn’t expose many methods and classes to us. The core class is Retrofit, and we just configure Retrofit and make requests. The rest is nothing to do with the top, just wait for the pullback. This greatly reduces the coupling degree of the system. For this notation we call appearance mode (facade mode).

Almost all good open source libraries have a facade. Glide. With () imageloader.load (). Having a front is convenient for memorization, low cost for learning and good for brand promotion. The face of Retrofit is retrofit.create()

Advantages:

1. Subsystem details are hidden from the client program, which reduces the client’s coupling with the subsystem and enables them to embrace changes

2. The appearance class encapsulates the interface of the subsystem, making the system easier to use.

Disadvantages:

1. Appearance class interfaces swell. Sometimes there are too many subsystems, which make the appearance class API more, and increase the user cost to some extent

2. The appearance class does not follow the open and close principle. When services change, you may need to modify the appearance class directly.

A dynamic proxy

The proxy pattern provides a proxy for other objects to control access to that object

Let’s talk about dynamic proxies. The dynamic and static proxies used similar scenarios in the past. Both want to do something before and after the delegate calls the method. If my proxy class had a lot of methods, I would have to write a lot of extra code, so I introduced dynamic proxies at this point.

You can handle different methods of different proxies by dynamically setting the delegate

Dynamic proxies use reflection to create proxy classes at run time.

Dynamic proxies are often used to do something before or after the actual operation

public class ProxyHandler implements InvocationHandler{
    private Object object;
    public ProxyHandler(Object object){
        this.object = object;
    }
 @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  System.out.println("Before invoke " + method.getName());  method.invoke(object, args);  System.out.println("After invoke " + method.getName());  return null;  } } Copy the code

Dynamic proxies in Retrofit are clever. It actually has no delegate at all. Because this method has no real implementation. Use dynamic proxies simply to get all the annotations on the method. All the work is done by the proxy

public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
 newClass<? >[] {service}, new InvocationHandler() {  private final Platform platform = Platform.get();  private final Object[] emptyArgs = new Object[0];   @Override  public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)  throws Throwable {  // If the method is a method from Object then defer to normal invocation.  if (method.getDeclaringClass() == Object.class) {  return method.invoke(this, args);  } args = args ! =null ? args : emptyArgs;  return platform.isDefaultMethod(method)  ? platform.invokeDefaultMethod(method, service, proxy, args)  : loadServiceMethod(method).invoke(args);  }  });  } Copy the code

Decorative pattern

The decorator pattern, also known as the wrapper pattern, dynamically adds additional responsibilities to an object.

The decorator pattern and the proxy pattern have in common enhanced functionality, but the proxy pattern is characterized by adding logical control, while the decorator pattern adds functionality dynamically

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
 this.callbackExecutor = callbackExecutor;  this.delegate = delegate;  }   @Override public void enqueue(final Callback<T> callback) {  Objects.requireNonNull(callback, "callback == null");   delegate.enqueue(newCallback<T>() {... } }  } Copy the code

You can think of ExecutorCallbackCall as a Wrapper, and the actual Source for executing requests is the OkHttpCall. The reason we have a Wrapper class is because we want to do something extra with the Source operation. The operation here is thread conversion, which switches the child thread to the main thread.

The enqueue() method is asynchronous, that is, when you call the enqueue method of OkHttpCall, the callback is in the child thread. If you want to receive the callback in the main thread, you need to use the Handler to convert it to the main thread. Executorcall is used to do just that. This, of course, is the way the original Retrofit uses thread switching. If you use rxjava, you will not use the ExecutorCallbackCall, but the rxjava Call

Adapter mode

The adapter pattern transforms the interface of one class into the expected interface of the client, allowing two classes to work together when their interfaces do not match (similar to adapters)

Back to Retrofit, why do we need adapters. Who’s the one being switched? Let’s look at the definition of a CallAdapter. Adapts a {@link Call} into the type of {@code T}. This Call is an OkHttpCall. Can’t we use it directly? Is there any special function to be implemented after being converted?

Let’s assume. Initially, Retrofit was intended to work only on Android, switching threads through a static agent called ExecutorCallbackCall. However, it turns out that RXJava is very useful, so you don’t need a Handler to switch threads. To do that, you have to switch. Convert OkHttpCall to rXJava (Scheduler) writing. Java8 (complete future) was later supported. This is probably the pattern.

The adapter pattern is that the existing OkHttpCall has to be called by a different standard platform. Designed an interface CallAdapter, let other platforms are doing different implementation to transform, so that it does not spend a lot of cost can be compatible with a platform

The strategy pattern

The policy pattern defines a series of algorithms, encapsulates each algorithm, and makes them interchangeable. The policy pattern lets the algorithm change independently of the customers that use it.

Usage Scenarios:

Multiple ways of dealing with the same type of problem are only different when specific behaviors are different

- privateCallAdapter<Observable<? >> getCallAdapter(Type returnType, Scheduler scheduler) {  Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);  
Class<? > rawObservableType = getRawType(observableType);  if (rawObservableType == Response.class) {    
    if(! (observableTypeinstanceof ParameterizedType)) {  
 throw new IllegalStateException("Response must be parameterized"  + " as Response<Foo> or Response<? extends Foo>");  }  Type responseType=getParameterUpperBound(0,(ParameterizedType)observableType);  return new ResponseCallAdapter(responseType, scheduler); }  if (rawObservableType == Result.class) {  if(! (observableTypeinstanceof ParameterizedType))  { throw new IllegalStateException("Result must be parameterized"  + " as Result<Foo> or Result<? extends Foo>");  }  Type responseType = getParameterUpperBound(0, (ParameterizedType)observableType);  return new ResultCallAdapter(responseType, scheduler); } return new SimpleCallAdapter(observableType, scheduler); } Copy the code

How was the CallAdapter established in RetroFIT?

It creates a concrete CallAdapter instance based on the returnType declared by the API method

Different algorithms are used for different policies. Different returnType declarations are set strategies

Differences between decorator mode and proxy mode

Both dynamic proxies and decorative patterns are used in Retrofit

Decorated patterns are very similar to static proxies and can be confusing

Proxy mode is more for logical control, decoration mode is more for enhancement of functionality

One big difference is whether the customer is aware that the agent has delegated another object

In the proxy mode, the target class is transparent to the client, whereas in the decorator mode, the client is enhanced for objects that are specifically targeted

The pseudocode is as follows:

// Proxy mode
public class Proxy implements Subject{
       private Subject subject;
       public Proxy(a){
             // The relationship is determined at compile time
 subject = new RealSubject();  }  public void doAction(a){ ... . subject.doAction(); ... . } }  // Decorate mode // Decorator mode public class Decorator implements Component{  private Component component;  public Decorator(Component component){  this.component = component  }  public void operation(a){ ... . component.operation(); ... . } } Copy the code

conclusion