This article has been authorized to WeChat public release code niche reprint please indicate the source: www.jianshu.com/p/dab7f5720…

1. Peruse Retrofit’s beauty 2. Peruse Retrofit’s beauty 2


The introduction

After reading about the beauty of Retrofit design in our last article, we looked at how the Builder pattern and the (dynamic) agent pattern are used in Retrofit and how they work. Today’s Retrofit framework is worth thinking about: the Abstract factory pattern

Abstract Factory pattern

Before taking a look at Retrofit’s abstract factory pattern in action, let’s take a look at the abstract factory pattern in action.

As we all know, app developers usually have user systems for app applications. A user system usually contains the following modules: 1. Login module. 2. Registration module. 3. Retrieve the password module. 4. User personal information module. These modules represent different types of products that the factory needs to produce. User system accounts may be the login method of app’s own account, password, or verification code of mobile phone SMS, or third-party platform accounts such as wechat, QQ, sina Weibo, etc. For user accounts of different platforms, we can regard them as different brand factories, such as app user account factories, wechat user account factories, QQ user account factories and Sina Weibo user account factories.

Is it clearer to design a user system in this way? Besides, different brands of factories are easy to replace, that is, to replace the login platform. The functional responsibilities of different product modules also become relatively single in line with the single principle of design mode.

Case implementation

  1. Firstly, each product interface is abstracted, and each module product has its own function
// ****************** ibaseuser.java, abstract user entity
/** * Abstract the user entity interface to facilitate generic design */
public interface IBaseUser {}// 1. ****************** iloginer.java, login module
/** * Log in to abstract interface *@param<U> User information */
public interface ILoginer<U extends IBaseUser> {

    / / login
    void login(U user);

    // Log out of the account
    void logout(U user);
}

****************** iregister. Java, register module
/** * Register account interface *@param<U> User information */
public interface IRegister<U extends IBaseUser> {
    // Register an account
    void registerAccount(U user);
}

****************** ifindPwder. Java, retrieve password module
/** * retrieve password interface *@param<U> User information */
public interface IFindPwder<U extends IBaseUser>  {
    // retrieve the password
    void findPwd(U user);
}

****************** iUserInfoer. Java, user information module
/** * User information related interface *@param<U> User information */
public interface IUserInfoer<U extends IBaseUser> {

    // Get user information
    U getUserInfo(a);

    // Save the user information
    void saveUserInfo(U userInfo);
}
Copy the code

The interface specification of these product modules is functionally abstract, which is basically enough for the user system of APP. Of course, the above interfaces can also be written in a unified interface file, and these modules are nested inside as sub-interfaces, which is for convenient management.

  1. Then there is the abstract interface of the factory to produce different products of different brands
/ / * * * * * * * * * * * * * * * * * * IUserSystemFactory in Java, the abstract factory interface
/** * User system abstract factory: login, registration, retrieve password, user information module */
public interface IUserSystemFactory {

    // Get the login module, login
    ILoginer getLoginer(a);

    // Get register module, register
    IRegister getRegister(a);

    // Retrieve the password module
    IFindPwder getFindPwder(a);

    // User information module
    IUserInfoer getUserInfoer(a);
}
Copy the code

It is mainly to obtain the product abstract interface object of different modules, so that the client can use the polymorphism of the module object of the factory.

  1. Factories and specific user system modules that implement different login methods

Because user systems interact with the UI most of the time, a base class is encapsulated to unify the Context and reduce unnecessary duplication of subclasses.

// *************BaseLoginer.java
/** * Base class of the login module *@param<U> User information */
public abstract class BaseLoginer<U extends IBaseUser> implements ILoginer<U> {

    private Context mContext;

    public BaseLoginer(Context context) {
        this.mContext = context; }}// *************BaseUserSystemFactory.java
/** * User system factory base class */
public abstract class BaseUserSystemFactory implements IUserSystemFactory {

    private Context mContext;

    public BaseUserSystemFactory(Context context) {
        this.mContext = context;
    }

    // Factory objects can get context
    public Context getContext(a){
        returnmContext; }}Copy the code

For example, the implementation when we log in using the app’s own user account

// ******************SystemAccountLoginer.java
/** * Log in using the application account */
public class SystemAccountLoginer extends BaseLoginer<User> {

    public SystemAccountLoginer(Context context) {
        super(context);
    }

    @Override
    public void login(User user) {
        / / login app
    }

    @Override
    public void logout(User user) {
        // Log out of the account}}// ******************SystemAccountFactory.java
/** * User system factory */
public class SystemAccountFactory extends BaseUserSystemFactory {
    private SystemAccountFactory(Context context) {
        super(context);
    }

    public static IUserSystemFactory create(Context context){
        return new SystemAccountFactory(context);
    }

    @Override
    public ILoginer getLoginer(a) {
        // Return the corresponding login product (app's own account platform login object)
        return new SystemAccountLoginer(getContext());
    }

    @Override
    public IRegister getRegister(a) {
        // Return the corresponding registered product (app's own account platform registered object)
        return null;
    }

    @Override
    public IFindPwder getFindPwder(a) {
        // Return the corresponding password retrieval product (app's own account platform password retrieval object)
        return null;
    }

    @Override
    public IUserInfoer getUserInfoer(a) {
        // Return the corresponding user information product (app's own account platform user information object)
        return null; }}Copy the code

Another example is using wechat to log in to the app

// ******************WeixinLoginer.java
/** * Use wechat to log in */
public class WeixinLoginer extends BaseLoginer<User> {
    public WeixinLoginer(Context context) {
        super(context);
    }

    @Override
    public void login(User user) {
        // Log in using wechat
    }

    @Override
    public void logout(User user) {
        // Log out}}// ******************WeixinFactory.java
/** * User system factory */
public class WeixinFactory extends BaseUserSystemFactory {
    private WeixinFactory(Context context) {
        super(context);
    }

    public static IUserSystemFactory create(Context context){
        return new WeixinFactory(context);
    }

    @Override
    public ILoginer getLoginer(a) {
        return new WeixinLoginer(getContext());
    }

    @Override
    public IRegister getRegister(a) {
        return null;
    }

    @Override
    public IFindPwder getFindPwder(a) {
        return null;
    }

    @Override
    public IUserInfoer getUserInfoer(a) {
        return null; }}Copy the code

Here I implemented the login product module, as well as the other modules. The use of the caller is also simple:

// client call
// Use your own platform
IUserSystemFactory factory = SystemAccountFactory.create(this);
// Use wechat platform account
// IUserSystemFactory weixinFactory = WeixinFactory.create(this);
User user = new User();
user.setUserId("1256339899879");
user.setPhone("13888888888");
// Log in to the app using your own account
factory.getLoginer().login(user);
// Use your own account to register
factory.getRegister().registerAccount(user);
// Use the password to retrieve your account
factory.getFindPwder().findPwd(user);
// Get user information
factory.getUserInfoer().getUserInfo();
Copy the code

It’s easy for the caller to care what platform account system is being used, not how it is implemented. It also divides the login, registration and access to user information from different platforms. Of course, different platforms may exit the current account in the same way. In this case, you can actually use BaseLoginer as the proxy object, the target interface is ILoginer, and the target object creates another class to implement the target interface, using the proxy mode.

Retrofit abstract factory application

We all know that network request traffic, when the server returns data, needs to be parsed and converted into entity objects that can be used directly, so that the Settings can be displayed on the UI. When we build Retrofit objects, we often inject a parser converter factory object into the builder.

new Retrofit.Builder()
                .baseUrl(AppConst.BASE_URL)
                .client(buildHttpClient())
                .addConverterFactory(FastJsonConverterFactory.create())
                .build();
Copy the code

The FastJsonConverterFactory. The create () to create is a Factory of abstract Factory object.

// Data converter abstract product class
// F is the input parameter, T is the output parameter (converted data type)
public interface Converter<F.T> {
  // Product conversion operation
  T convert(F value) throws IOException;

  // Abstract factory class
  abstract class Factory {
    // Factory produced request response converter products
    publicConverter<ResponseBody, ? > responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {return null;
    }

    // Factory-produced request initiated converter products
    publicConverter<? , RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {return null;
    }

   // A factory-made converter product for converting string data types
    publicConverter<? , String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {return null; }}}Copy the code

Let’s look at the factory implementation class that uses FastJson as a converter:

public class FastJsonConverterFactory extends Converter.Factory {
  // Create a factory object
  public static FastJsonConverterFactory create(a) {
    return new FastJsonConverterFactory();
  }

  private FastJsonConverterFactory(a) {}@Override
  publicConverter<ResponseBody, ? > responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {return new FastJsonResponseBodyConverter<>(type, mParserConfig, featureValues, features);
  }

  @Override
  publicConverter<? , RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {return newFastJsonRequestBodyConverter<>(serializeConfig, serializerFeatures); }}Copy the code

By encapsulating a create method to create the factory object, external callers do not need to relate how the factory object was created. This is the same as the example I gave above. The responseBodyConverter and requestBodyConverter methods create the responseBodyConverter and requestBodyConverter products respectively.

Take a look at FastJsonRequestBodyConverter request initiated the realization of the conversion products:

// Implement the abstract product class of the converter. The input parameter is RequestBody, and the result returned is generic T
final class FastJsonRequestBodyConverter<T> implements Converter<T.RequestBody> {
  private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
  private SerializeConfig serializeConfig;
  private SerializerFeature[] serializerFeatures;

  FastJsonRequestBodyConverter(SerializeConfig config, SerializerFeature... features) {
    serializeConfig = config;
    serializerFeatures = features;
  }

  @Override
  public RequestBody convert(T value) throws IOException {
    byte[] content;
    if(serializeConfig ! =null) {
      if(serializerFeatures ! =null) {
        content = JSON.toJSONBytes(value, serializeConfig, serializerFeatures);
      } else{ content = JSON.toJSONBytes(value, serializeConfig); }}else {
      if(serializerFeatures ! =null) {
        content = JSON.toJSONBytes(value, serializerFeatures);
      } else{ content = JSON.toJSONBytes(value); }}returnRequestBody.create(MEDIA_TYPE, content); }}Copy the code

Implement the converter abstract product interface class, the input parameter is RequestBody, return the result is generic T (because the request parameters are specific to the business as the framework can not determine, so use generic instead), This FastJsonRequestBodyConverter product function is to convert conversion function, here using the alibaba json parsing library fastJson to transform, the specific implementation is through json. ToJSONBytes conversion from json byte array, Requestbody.create is then handed over to OkHttp to build a RequestBody and the requested multimedia type is in json format. Implementation in OkHttp:

public static RequestBody create(final MediaType contentType, final byte[] content,
      final int offset, final int byteCount) {
    if (content == null) throw new NullPointerException("content == null");
    Util.checkOffsetAndCount(content.length, offset, byteCount);
    return new RequestBody() {
      @Override public MediaType contentType(a) {
        return contentType;
      }

      @Override public long contentLength(a) {
        return byteCount;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        // content The request parameters are written to Okio's BufferedSinksink.write(content, offset, byteCount); }}; }Copy the code

You’ll notice that RequestBody is an abstract class and writeTo is an abstract method, so there must be a place to call this method. The best place to construct a request is when the request is made, call.enqueue(callback). An asynchronous request is made through the enqueue, but call is the interface and does not know the implementation class. Another option is to go backwards, place the cursor on the writeTo method and press the combination key (where writeTo is used) : CTRL + Alt + F7:

Apparently last ReqeustBuilder, request to build class, to go in is ContentTypeOverridingRequestBody, it is a proxy class, The target object is an object of its internal RequestBody this object we suspect is the RequestBody FastJsonRequestBodyConverter converter can convert to create above. Take a look at ContentTypeOverridingRequestBody in RequestBuild build (constructed) method of use:

// The constructor pattern is used to construct an OkHttp Request object because of the complexity of initializing the Request object
class RequestBuild{
  Request build(a) {
    // Obviously we had a FastJson request that was passed in when we were building Retrofit, so let's just say the task body has a value not equal to null
    RequestBody body = this.body;
    if (body == null) {
      // Try to pull from one of the builders.
      if(formBuilder ! =null) {
        body = formBuilder.build();
      } else if(multipartBuilder ! =null) {
        body = multipartBuilder.build();
      } else if (hasBody) {
        // Body is absent, make an empty body.
        body = RequestBody.create(null.new byte[0]); }}/ / here to make the body with a layer of agent, before the actual target interface or FastJsonRequestBodyConverter created body target to call myself
    // The body of the proxy object is then given to the Request to build the request-originating object.
    MediaType contentType = this.contentType;
    if(contentType ! =null) {
      if(body ! =null) {
        body = new ContentTypeOverridingRequestBody(body, contentType);
      } else {
        requestBuilder.addHeader("Content-Type", contentType.toString()); }}// The Request object is finally created by the constructor of OkHttp's Request class itself
    returnrequestBuilder .url(url) .method(method, body) .build(); }}Copy the code

The caller to build() of a RequestBuild is ServiceMethod’s toRequest() method:

Request toRequest(Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);
    / /... Omit code
    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }
    return requestBuilder.build();
  }
Copy the code

Take a look at the apply method, which is the abstract method of ParameterHandler and has many implementations of creating parameters:

@Override void apply(RequestBuilder builder, T value) {
      if (value == null) {
        throw new IllegalArgumentException("Body parameter value must not be null.");
      }
      RequestBody body;
      try {
        The convert method is where the FastJSON factory transformation above creates the RequestBody object that the request initiates
        body = converter.convert(value);
      } catch (IOException e) {
        throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
      }
      // Set the created RequestBody object to the RequestBuild builder. This is the builder's benefit (it is not easy to initialize a Request object, and the timing and location of initialization properties vary)
      builder.setBody(body);
    }
Copy the code

The toRequest() method of ServiceMethod is called by createRawCall() of OkHttpCall

private okhttp3.Call createRawCall(a) throws IOException {
    Request request = serviceMethod.toRequest(args);
    okhttp3.Call call = serviceMethod.callFactory.newCall(request);
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }
Copy the code

The above code means that a request origami object is created with some parameters, and then a call object is created with a factory object that is used to initiate the request okHttp3. Let’s look at the call to the createRawCall() method, which is called in three places:

// a synchronous request method
public synchronized Request request(a) {}

// Asynchronous request method, but no request callback
public Response<T> execute(a) throws IOException {}

// An asynchronous request method, handled by a request callback interface object
public void enqueue(final Callback<T> callback) {}
Copy the code

Enqueue (callback) : enqueue(callback) : enqueue(callback);

@Override public void enqueue(final Callback<T> callback) {
    // If the callback is null, the null pointer is not null
    if (callback == null) throw new NullPointerException("callback == null");

    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;    // There may be an error in the creation
      if (call == null && failure == null) {
        try {
          // When used in the first build, a call is created
          call = rawCall = createRawCall();
        } catch(Throwable t) { failure = creationFailure = t; }}}if(failure ! =null) {
      // The callback request fails if it fails
      callback.onFailure(this, failure);
      return;
    }

    if (canceled) {
     // If the request is cancelled, it will be cancelled
      call.cancel();
    }

    // This is where the request is actually initiated, passing the request to the underlying OkHttp.
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
          throws IOException {
        Response<T> response;
        try {
          // When the request returns successfully, the response is parsed
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        // Inform that the callback request was successful
        callSuccess(response);
      }
      
      // The request failed
      @Override public void onFailure(okhttp3.Call call, IOException e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch(Throwable t) { t.printStackTrace(); }}private void callFailure(Throwable e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch(Throwable t) { t.printStackTrace(); }}private void callSuccess(Response<T> response) {
        try {
          // The callback to the business request caller notifies the request of success
          callback.onResponse(OkHttpCall.this, response);
        } catch(Throwable t) { t.printStackTrace(); }}}); }Copy the code

This reverse analysis, I do not know whether there is a clearer point, comb the next:

  1. Retrofit was built with a FastJson factory object set for it.

  2. Call.enqueue (callback) is an OkHttpCall object.

  3. CreateRawCall is invoked when enQueue is created

  4. CreateRawCall calls serviceMethod’s toRequest method first

  5. In the toRequest method, create a RequestBuild object, And convert a RequestBody set to the RequestBuild object using the FastJsonRequestConverter created by the FastJson factory. And eventually create the Request object through the builder pattern.

  6. A call is then created through the callFactory for the request, which is ultimately sent to OkHTTP’s enQueue method to initiate the actual network request.


conclusion

Today’s length is also quite long, mainly explains the use of abstract factory design pattern, specific examples in the development of a more practical multi-platform login user system module, of course, this is only an example of the actual project needs to improve a lot of. There are many common examples, such as: switching of various payment methods, switching of various map SDKS, switching of various network frameworks, switching of various persistent data storage methods, switching of various data processing methods, switching of various picture loaders and so on.

The rest of the article focuses on the use of the abstract factory in Retrofit and briefly analyzes how Retrofit builds and initiates requests.


I’m JerryloveEmily, thanks for reading,

If you like it, “❤ Like it.”

Encourage and do not spend money, you see, I continue to write ~

Non-book users can click the three “… “in the upper right corner. “And” Open it in Safari “to like it