preface

I have been parsing open source frameworks commonly used in Android (racking my brains), as for OkHttp and Retrofit in the network framework (at present), there are a lot of design patterns used in Retrofit, so in order to better understand the source code, here is a review of design patterns in advance

Take a look at what design patterns are used in Retrofit

1.Retrofit Build process Builder mode, Factory method mode 2. Create network request interface instance Process facade, proxy, singleton, Policy, Decorator (Builder) 3. Generate and execute the request process adapter pattern (broker pattern, decorator pattern)Copy the code

Factory method pattern

For complex object creation, define a class that creates the object and let subclasses decide which class to instantiate

implementation

Now let’s actually see how the factory mode is implemented

  • Create products that implement common interfaces
interface Product {
    val name:String
    fun show(a)
}
Copy the code
  • Create multiple specific products, need to inherit Product
// Product A
class ProductA(override val name: String = "ProductA") : Product {
    override fun show(a) {
        Log.e("ProductA"."show: ")}}// Product B
class ProductB(override val name: String = "ProductB") : Product {
    override fun show(a) {
        Log.e("ProductB"."show: ")}}Copy the code
  • Create abstract factories to implement common interfaces
interface Factory {
    fun makeProduct(a) : Product
}
Copy the code
  • Implement a concrete Factory, inheriting Factory
class FactoryA : Factory {
    override fun makeProduct(a): Product {
        return ProductA()
    }
}

class FactoryB:Factory {
    override fun makeProduct(a): Product {
        return ProductB()
    }

}
Copy the code

Apps on Android

Take BitmapFactory as an example to generate

// Generate the Bitmap object's factory class BitmapFactory
public class BitmapFactory {...public static Bitmap decodeFile(String pathName) {...}...public static Bitmap decodeResource(Resources res, int id, Options opts) {
        validate(opts);
        Bitmap bm = null;
        InputStream is = null; 
        
        try {
            final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts); }...returnbm; }...}Copy the code

conclusion

  • advantages
  1. In accordance with the principle of single responsibility, each specific plant is only responsible for the corresponding product
  2. When we need to add a product, we just need to add a product concrete class and the corresponding factory subclass
  • disadvantages
  1. A specific factory class can only create one specific product. If we need to add a new product, we need to create a new corresponding factory class, which will undoubtedly add overhead

Builder model

Separating the construction of a complex object from its representation allows the same construction process to create different representations, suitable for situations where the initialized object is complex and has many parameters.

implementation

  • How do you create a dialog? How do you create a dialog? How do you build a complex object
  fun createDialog(context: Context) {
        val dialog = Dialog(context).apply {
            setTitle("DialogA")
            setCancelable(true)
            setCanceledOnTouchOutside(true)
            setContentView(contentView)
        }
    }
Copy the code
  • Use Type-Safe Builders when you see the above Lambda expression calling ** dialok.show ()** and other methods that have nothing to do with building an object, or if you don’t want to expose the constructor and just want to build the object with the Builder
class CarsBuilder
    (
    val model: String? .val name: String? .val year: Int
) {

    // The types-Builder is used for the creation
     constructor(builder: Builder) : this(builder.model, builder.name,builder.year)

    class Builder {
        var model:String? = null
        var name:String? = null
        var year:Int = -1

        fun build(a) = CarsBuilder(this)
    }

    companion object {
        inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build()
    }
}

val cars = CarsBuilder.build {
    model = "Toyota Camry"
    name = "Double with 2021"
    year = 2021
}

Copy the code
  • The advantage of this is that the member function in the Builder class returns the Builder object itself, allowing it to support chain calls. If you only want to pass in some parameters, you can write them in the build definition. If you need to use new configuration parameters later, you can add them directly, which also improves the extensibility and readability of the code

Now let’s briefly summarize the creation of the Builder pattern

  1. Define the inner class Builder, whose member variables are the same as the outer class
  2. The constructor assigns a value to a member variable and returns itself this
  3. Create an external create method, build(), that uses type-Safe Builders to create. The external class provides a private constructor that the inner class calls to complete the assignment of member variables to the values of the corresponding variables in the Builder object

Apps on Android

  • Dialog box creation

If you look at AlertDialog. Builder()

 fun showDialog(context: Context) {
        val builder =  AlertDialog.Builder(context)
            .setTitle("TEST")
            .setMessage("Test")

        val dialog = builder.create()
        dialog.show()
    }
Copy the code

AlertDialog.java

public class AlertDialog extends Dialog implements DialogInterface {...public static class Builder {
        private finalAlertController.AlertParams P; ...public Builder(Context context) {
            this(context, resolveDialogTheme(context, ResourceId.ID_NULL)); }...public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this; }...public Builder setMessage(CharSequence message) {
            P.mMessage = message;
            return this; }...public AlertDialog create(a) {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, 0.false); P.apply(dialog.mAlert); ...returndialog; }...}}Copy the code
  • Network framework Okhttp(previously written open source framework mentioned)
  val okHttpClient = OkHttpClient()
  val request = Request.Builder()
            .url(BASE_URL)
            .build()
Copy the code

conclusion

  • advantages
  1. The benefit of the Builder pattern is that it takes the construction and presentation of the configuration out of the way and avoids writing too many setters and getters.
  2. Builder creates commonly used chained calls so the code is concise and easy to understand
  • disadvantages

Because of both internal and external references, memory consumption is relatively high, but for current mobile applications, this is not a problem

The singleton pattern

Ensuring that there is only one instance of a class and that it is automatically instantiated to provide that instance to the entire system avoids creating multiple objects that consume resources.

The above singleton definition is pretty official, but what are the specific benefits of mobile development? Because it only has one instance all the way through, one instance at a time greatly improves our performance

implementation

There are two ways to realize singleton pattern: hungry man pattern and lazy man pattern

The hungry mode

For the Kotlin implementation, just call the Object implementation

object SingleTest {
  ...
}

/ / kotlin calls
SingleTest.xx()
/ / Java calls
SingleTest.INSTANCE.xx()
Copy the code

This is actually the same implementation principle as hangry, and you can actually decompile it

public final class SingleTest {
   @NotNull
   public static final SingleTest INSTANCE;

   private SingleTest(a) {}static {
      SingleTest var0 = newSingleTest(); INSTANCE = var0; }}Copy the code

Constractor is not allowed in this object class. The INSTANCE constructor is not allowed in this object class. Resources are wasted because the object is created initially but not called; If you do time-consuming operations inside, the load will be slow

Lazy mode

This singleton is instantiated as you call it, and Lazy is a good fit in Kotlin. See how it works

class SingleTon constructor(a){

   companion object {
       val instance: SingleTon by lazy { SingleTon() }
   }
}
Copy the code

Doesn’t that seem a lot cleaner, but what does the Lazy attribute actually do? In fact, it is thread-safe by default, synchronized modifiers, double-checked locking, and static inner classes are all thread-safe slobs

public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
private class SynchronizedLazyImpl<out T> (initializer: () - >T.lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE    // Declare it as volatile
    // final field is required to enable safe publication of constructed instance
    privateval lock = lock ? :this
    override val value: T
        get(a) {
            val _v1 = _value
            if (_v1 ! == UNINITIALIZED_VALUE) {  // First check
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            return synchronized(lock) {  // Lock lock
                val _v2 = _value
                if (_v2 ! == UNINITIALIZED_VALUE) {  // Second check
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                }
                else{ val typedValue = initializer!! () _value = typedValue initializer =null
                    typedValue
                }
            }
        }
    ...
}
Copy the code
  • As you can see, the Lazy code also uses double check mode for Lazy initialization, which is thread-safe and relatively simple to call
  • Which can be introduced into three LazyThreadSafetyMode thread safe mode, the default is LazyThreadSafetyMode SYNCHRONIZED synchronous lock
    • SYNCHRONIZED, which uses a lock to ensure that only one thread can be instantiated
    • PUBLICATION, which allows multiple threads to instantiate, but the first thread returned will be used as an initialized instance of lazy.
    • NONE, which is not thread-safe, has no guarantees and no overhead (I basically don’t use it)

Apps on Android

In input method development, InputMethodManager is used. This class is initialized with a singleton pattern that controls the display and hiding of input method panels. Here is the code

InputMethodManager.java

/**
* Retrieve the global InputMethodManager instance, creating it if it
* doesn't already exist.
* @hide* /
public static InputMethodManager getInstance(a) {
    synchronized (InputMethodManager.class) {
        if (sInstance == null) {
            try {
                sInstance = new InputMethodManager(Looper.getMainLooper());
            } catch (ServiceNotFoundException e) {
                throw newIllegalStateException(e); }}returnsInstance; }}Copy the code

conclusion

The advantages and disadvantages

  • advantages
  1. In the singleton pattern, there is only one instance of an active singleton, and all instantiations of a singleton class yield the same instance. This prevents other objects from instantiating it and ensures that all objects have access to an instance
  2. Provides controlled access to a unique instance.
  3. Since there is only one object in system memory, system resources are saved, and the singleton pattern can certainly improve system performance when objects are frequently created and destroyed.
  4. Avoid multiple occupancy of shared resources.
  • disadvantages
  1. If you want to send changes to the same object in different scenarios, using singleton raises data errors and does not save each other’s state
  2. If you use singletons that are too onerous, you violate the single responsibility principle to some extent (see object-oriented design Principles for details)
  3. Do not generalize to the singleton pattern. If the object you instantiate is not called, it may be GC and the object state will be lost

Applicable scenario

Because only one object is instantiated, the singleton pattern can be used in any scenario where the object is common

  1. Objects that require frequent access to the database

  2. An object that takes too much time or resources to create but is often used.

  3. Stateful tool class objects.

  4. Objects that are frequently called and destroyed