preface

Many people think that being an architect is a lofty position, and only the top skilled person can be qualified for it, but in fact it is not so lofty. Most architects are just rich in development experience, love learning, and good at knowledge transfer and summary. Application architecture is very mature and has a lot of experience for us to learn from. We can learn the architecture ideas of large projects from Android architecture, and we can also learn the essence of framework from Android parts, such as binder design and framework design. We can also drill down into the details of Android code to see the implementation, see what scenarios use what design patterns, and how to write more elegant code. In addition to Android, we can also learn from Github and other open source projects and articles about excellent architecture ideas, such as layered architecture, microkernel architecture, plug-in architecture, etc.

I will explain my understanding of architecture through several articles, which are divided into three main topics: what is architecture, what is framework, and what is design pattern.

Architecture, framework, design patterns, these three points, from the project as a whole to the details of the code to reduce the granularity of the process. Architecture is a hill-house perspective. In addition to how to solve the big demand, we also need to consider the technical solution, scalability, performance, safety, cost and other dimensions. To use the analogy of building a house, architecture is the process of finalizing the housing style, building materials, selecting the construction team and drawing the design drawings. And when that’s done, the framework comes in, and the framework is used to figure out how to design locally, for example, should we use MVP or MVVM for business function development, should we use Retrofit or Okhttp for accessing the network, and so on. A project involves multiple frameworks, and a framework is like determining how to build a foundation when building a house. How to build scaffolding and other local processes, and finally, what design patterns to use when writing code can make code more elegant and decoupled, just like how to install Windows when building a house, how to install wall tiles and so on.

In this article, I will focus on the first topic: what are design patterns? This topic is also the easiest to start with. Every new developer can use several design patterns in project development, even if they have not used design patterns, they have seen a variety of design patterns in code.

Design patterns

When we do things, many things are routine, the routine is to summarize the experience of his predecessors, what circumstances do as what routines, usually doing things very well, the code is not exceptional also, in the process of writing code, written many scenes have a fixed pattern, this pattern is the design pattern, Design pattern is the GOF summed up a set of developers often face scene in the actual development of the solution, not invented GOF design patterns, GOF just summarizes these routines, through the adoption of these routines, design patterns, we write the code more reliability, decoupling, higher reusability, and more easy to understand others, There are three main types of design patterns: creative, structural, and behavioral.

Let’s start with the creative design pattern, which is the simplest design pattern.

Create a type

The creation design pattern is mainly used to solve the problem of how to create objects. Although we can directly create an object through new, it is not a good way to create objects directly in many scenarios. In this case, the use of the creation design pattern can help us to solve the problem. The creation design pattern includes singleton, prototype, factory method, abstract factory and builder. Their main characteristics are the separation of object creation and use.

Let’s take a look at how to use the creative design pattern and what scenario to use the creative design pattern in.

Factory methods and abstract factories

The author once developed a sharing function. After clicking the sharing panel, the corresponding object instance needs to be created according to the sharing icon clicked, such as QQ, wechat, weibo, etc. If the design pattern is not used, the corresponding type of shared instance needs to be created in the click event of each icon, as shown in the pseudocode below.

wxIcon.setOnClickListener(()->{
	WXShare wxShare = new WXShare;
	wxShare.share();
})

qqIcon.setOnClickListener(()->{
	QQShare qqShare = new QQShare;
	QQShare.share();
})

wbIcon.setOnClickListener(()->{
	WBShare wxShare = new WBShare;
	wbShare.share();
})
Copy the code

This way of creating objects has one disadvantage: As to share the type of more and more, need to create a Shared instance is also more and more directly, that means that the object in the object needs to deal with more and more, it has violated the single responsibility principle, share the panel’s duty is to share, to create various types of Shared instance, not to share the duties of a panel, the Shared instance creation is very simple, However, in many cases, object creation is complicated and requires a lot of data initialization, so creating shared instances directly in the click event is not a good approach.

The above scenario can be modified using the factory method. The transformation steps are as follows

  1. Start by defining the sharing interface and the entity class that implements the sharing interface
public interface IShare{
	void share(a);
}

public WXshare implements IShare{
	public void share(a){
		//doSomething;}}public QQShare implements IShare{
	public void share(a){
		//doSomething;}}public WBShare implements IShare{
	public void share(a){
		//doSomething;}}Copy the code
  1. Define the factory class ShareFactory
public class ShareFactory{
    public IShare createShareInstance(String shareType){     
      if(shapeType.equalsIgnoreCase("WX")) {return new WXshare();
      } else if(shapeType.equalsIgnoreCase("QQ")) {return new QQShare();
      } else if(shapeType.equalsIgnoreCase("WB")) {return new WBShare();
      }
      return null; }}Copy the code
  1. Using factory Type
ShareFactory shareFactory = new ShareFactory();

wxIcon.setOnClickListener(()->{
	IShare wxShare = shareFactory.createShareInstance("WX");
	wxShare.share();
})

qqIcon.setOnClickListener(()->{
	IShare qqShare = shareFactory.createShareInstance("QQ");
	QQShare.share();
})

wbIcon.setOnClickListener(()->{
	IShare wxShare = shareFactory.createShareInstance("WB");
	wbShare.share();
})
Copy the code

As you can see, with the factory method, object creation is all done in the factory method object, which also conforms to the single responsibility principle: object creation is done by the object that does it.

Now we’re going to look at what an abstract factory is, and we’re going to put it together with the factory method because they’re very similar, but the difference is that an abstract factory goes from creating object instances of one type to creating object instances of multiple types. Taking the sharing example I mentioned above, after developing the sharing function, I came up with a new requirement: access to third-party quick login.

In the login interface, when I click the QQ icon, I will enter the QQ fast login, click the wechat icon, I will enter the wechat fast login, click the Weibo icon, I will enter the Weibo fast login. We could also use the factory method at this point to create a new factory method for the third party logged-in instance creation, but there is a better way to create: put the logged-in instance creation and the shared instance creation in the same factory method, which is the abstract factory. Why can we do that? Because third-party sharing and third-party logins belong to the same product family, they both reference the same THIRD-PARTY SDK, and the benefit of doing so is to comply with the single responsibility principle. Let’s look at how to use an abstract factory.

  1. You then create the interface and implementation class for the login
public interface ILogin{
	void login(a);
}

public WXLogin implements Login{
	public void login(a){
		//doSomething;}}public QQLogin implements Login{
	public void login(a){
		//doSomething;}}public WBLogin implements Login{
	public void login(a){
		//doSomething;}}Copy the code
  1. Create abstract factories for sharing and logging
public abstract class AbstractFactory {
   public abstract IShare createShareInstance(String type);
   public abstract ILogin createLoginInstance(String type) ;
}
Copy the code
  1. Extend the abstract factory to create shared and logged entity objects
public class ShareFactory extends AbstractFactory {
    
   @Override
   public IShare createShareInstance(String type){
      if(shapeType == null) {return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")) {return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")) {return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")) {return new Square();
      }
      return null;
   }
   
   @Override
   public ILogin createLoginInstance(String type) {
      return null; }}public class LoginFactory extends AbstractFactory {
    
   @Override
   public IShare createShareInstance(String type){
      return null;
   }
   
   @Override
   public ILogin createLoginInstance(String type) {
      if(color == null) {return null;
      }        
      if(color.equalsIgnoreCase("RED")) {return new Red();
      } else if(color.equalsIgnoreCase("GREEN")) {return new Green();
      } else if(color.equalsIgnoreCase("BLUE")) {return new Blue();
      }
      return null; }}Copy the code
  1. Create a factory generator to create different types of factories
public class FactoryProducer {
   public static AbstractFactory getFactory(String scene){
      if(scene.equalsIgnoreCase("Login")) {return new LoginFactory();
      } else if(scene.equalsIgnoreCase("Share")) {return new ShareFactory();
      }
      return null; }}Copy the code

At this point, the abstract factory is created. We just need to get the LoginFactory from FactoryProducer in the login scenario to create instances of various login objects. In shared scenarios, FactoryProducer obtains the ShareFactory to create instances of various types of shared objects.

Summary of Usage Scenarios

Let’s summarize the factory method and use scenarios for abstract factories

The factory method
  1. According to different conditions, we need to create a different instance, and create the object is complicated, can use the factory method, sharing the above example is such a scenario, but create the object in the above example is relatively simple, in actual use, if the created object is very simple, need only new can create, without complex initialization, We can also create objects directly, using the factory method adds code and complexity.
The abstract factory
  1. The use scenario of abstract factory needs to meet two scenarios. The first is to meet the use scenario of factory method, that is, different instances need to be created according to different conditions, and at the same time to meet the second scenario, that is, products need to be created with multiple product families.

Android app example – LayoutInflater

This is an example of an Android factory: LayoutInflater. We have all used the inflate of a LayoutInflater to create a View. This View is created as follows

/frameworks/base/core/java/android/view/LayoutInflater.java

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally{ parser.close(); }}Copy the code

The XmlResourceParser is obtained in the Inflate method, which is dedicated to parsing the XML layout file, and the inflate overloaded method is called

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    View result = root;

    try {
        int type;
        // Loop through the XML file until the end flag is read out of the loop
        while((type = parser.next()) ! = XmlPullParser.START_TAG && type ! = XmlPullParser.END_DOCUMENT) {// Empty
        }


        final String name = parser.getName();

        // Check whether it is a merge tag. Merge layout must not satisfy the parent view
        if (TAG_MERGE.equals(name)) {
            if (root == null| |! attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "
                                           + "ViewGroup root and attachToRoot=true");
            }

            // Parse the merge layout tag
            rInflate(parser, root, inflaterContext, attrs, false);
        } else {
            // Create a view based on name, the parsed XML tag
            final View temp = createViewFromTag(root, name, inflaterContext, attrs);
            ……
        }

    } catch (XmlPullParserException e) {
        ……
    } catch(the Exception e) {... }finally {
        ……
    }
    return result;
}
Copy the code

If we look at createViewFromTag, which is the function that creates the View, we know that there are many subclasses of View, such as TextView, ListView, ImageView and so on. Let’s look at the implementation inside

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                       boolean ignoreThemeAttr) {...try {
        View view;
        // Call factory to create the View
        if(mFactory2 ! =null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if(mFactory ! =null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

        if (view == null&& mPrivateFactory ! =null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        // If no factory creates the view, call its own createView method
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('. ')) {               
                    view = onCreateView(parent, name, attrs);
                } else {
                    / / create the View
                    view = createView(name, null, attrs); }}finally {
                mConstructorArgs[0] = lastContext; }}return view;
    } catch (InflateException e) {
       ……
    } catch(a ClassNotFoundException e) {... }catch(the Exception e) {... }}Copy the code

The View is not actually created using a factory method. Instead, it is created by calling reflection from the parsed View tag. The create function is implemented in createView. Because there are so many subclasses of View, the factory method can be very large, so it’s a little bit simpler to use the reflection code. The implementation of createView is not going to be described here. In fact, factory is used to create custom View. If we have a custom tag in XML, such as Div, Android cannot create a View corresponding to the tag. We can create a factory that creates a View based on the Div tag, and then set that factory to the LayoutInflater, so that the LayoutInflater can extend the View that creates the Div XML tag.

Through this example, we can see that the factory method or the abstract factory, not only can let the object creation more in line with the single responsibility, to achieve the object creation and use of decoupling, we can also use this kind of design pattern, provides the ability of an extension, such as the example above is through the factory provides the create custom tag trying to expand capacity.

The prototype

The author had developed a drawing board function, sketchpad can draw a rectangle, square, circular, etc., on the drawing board to draw, choice of different shapes, to create the corresponding object instance, in the shape of a lot of data need to be initialized when the instance is created, such as color, brush size, etc., considerable trouble, such a scenario, we can use the factory method to optimize above, Instead of talking about the factory approach, I’m going to talk about another design pattern: the prototype pattern. The prototype pattern can be used to quickly create objects and make object creation more efficient.

  1. Create abstractions of shapes that all need to inherit from the Cloneable interface
public abstract class Shape implements Cloneable {
   
   private String id;
   protected  String type;
   private int color;
   private int paintSize;
   
   abstract void draw(a);
   
   public String getType(a){
      return type;
   }
    
   public String setType(String type){
       this.type = type;
   }
   
   public String getId(a) {
      return id;
   }
   
   public void setId(String id) {
      this.id = id;
   }
    
   // Omit the other attribute initializer methods...public Object clone(a) {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      returnclone; }}Copy the code
  1. Extended Abstract class
public class Rectangle extends Shape {
 
   public Rectangle(a){
     type = "Rectangle";
   }
 
   @Override
   public void draw(a) {
      System.out.println("Inside Rectangle::draw() method."); }}public class Square extends Shape {
 
   public Square(a){
     type = "Square";
   }
 
   @Override
   public void draw(a) {
      System.out.println("Inside Square::draw() method."); }}public class Circle extends Shape {
 
   public Circle(a){
     type = "Circle";
   }
 
   @Override
   public void draw(a) {
      System.out.println("Inside Circle::draw() method."); }}Copy the code
  1. Store extended entity classes in a collection container
public class ShapeCache {
    
   private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>();
 
   public static Shape getShape(String shapeId) {
      Shape cachedShape = shapeMap.get(shapeId);
      return (Shape) cachedShape.clone();
   }
 
   public static void initShapeCache(a) {
      Circle circle = new Circle();
      circle.setId("1");
      shapeMap.put(circle.getId(),circle);
 
      Square square = new Square();
      square.setId("2");
      shapeMap.put(square.getId(),square);
 
      Rectangle rectangle = new Rectangle();
      rectangle.setId("3"); shapeMap.put(rectangle.getId(),rectangle); }}Copy the code
  1. At this point, we can get the prototype of our shape in ShapeCache and quickly create objects corresponding to the shape
public class PrototypePatternDemo {
   public static void main(String[] args) {
      ShapeCache.loadCache();
 
      Shape clonedShape = (Shape) ShapeCache.getShape("1");
      System.out.println("Shape : " + clonedShape.getType());        
 
      Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
      System.out.println("Shape : " + clonedShape2.getType());        
 
      Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
      System.out.println("Shape : "+ clonedShape3.getType()); }}Copy the code

Also creating objects, in the example above, what is the difference between creating objects through prototypes and creating objects through factory methods? The difference mainly lies in performance. The prototype creates objects by copying existing objects. In this way, the performance is more efficient than creating new objects.

Usage scenarios

Here’s a summary of the usage scenarios for the prototype pattern

  1. The prototype pattern can be used when creating objects is complex and requires initializing a lot of data and resources, such as the example mentioned above

  2. Prototype mode can be used when there are performance requirements for creating objects, and objects created with Clone have a lower performance cost than objects created with New

  3. When security is required, that is, when an object needs to be made available to other objects, and individual callers may need to modify its value, the prototype pattern can be used, in which case the value of the copied object can be modified without making changes to the original object.

Android app example — Transition

For example, the Transition animation initialization is a difficult task. You need to set the animation mode, time, type, interpolator, etc. For such a complex object, Android generally implements the Clone function, which allows us to create objects from prototypes. Let’s look at the Clone method implemented by Transition.

/frameworks/support/transition/src/android/support/transition/Transition.java

@Override
public Transition clone(a) {
    try {
        Transition clone = (Transition) super.clone();
        clone.mAnimators = new ArrayList<>();
        clone.mStartValues = new TransitionValuesMaps();
        clone.mEndValues = new TransitionValuesMaps();
        clone.mStartValuesList = null;
        clone.mEndValuesList = null;
        return clone;
    } catch (CloneNotSupportedException e) {
        return null; }}Copy the code

Transition clone initializes mAnimators, mStartValues, and mEndValues. Let’s look at an example of PopupWindow creating an exit animation through a prototype.

public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    mContext = context;
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    final TypedArray a = context.obtainStyledAttributes(
        attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
    final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
    mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
    mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);

   
    if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {
        final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);
        if (animStyle == R.style.Animation_PopupWindow) {
            mAnimationStyle = ANIMATION_STYLE_DEFAULT;
        } else{ mAnimationStyle = animStyle; }}else {
        mAnimationStyle = ANIMATION_STYLE_DEFAULT;
    }

    // Initialize the animation
    final Transition enterTransition = getTransition(a.getResourceId(
        R.styleable.PopupWindow_popupEnterTransition, 0));
    final Transition exitTransition;
    if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {
        exitTransition = getTransition(a.getResourceId(
            R.styleable.PopupWindow_popupExitTransition, 0));
    } else {
        // Create exit animation by copy
        exitTransition = enterTransition == null ? null : enterTransition.clone();
    }

    a.recycle();

    setEnterTransition(enterTransition);
    setExitTransition(exitTransition);
    setBackgroundDrawable(bg);
}
Copy the code

As you can see from the example above, when PopupWindow does not manually set the exit animation, it copies the entry animation as the default exit animation.

The builders

Take the sharing function as an example. There are many types of sharing, including pictures, text, URL links and videos. There are different types of sharing according to different sharing scenarios

public class Share {
    private final String pic;
    private final String text;
    private final String url;
    private final String video;
    
    public Share(String pic){
        this.Share(pic,"".""."");
    }
    
    public Share(String text){
        this.Share("",text,""."");
    }
    
    public Share(String url){
        this.Share(""."",url,"");
    }
    
    public Share(String video){
        this.Share("".""."",video);
    }
    
    public Share(String pic,String text){
        this.Share(pic,text,""."");
    }
    
    // omit the remaining constructors...public Share(String pic,String text,String url,String video){
        this.pic = pic;
        this.text = text;
        this.url = url;
        this.video = video; }}Copy the code

From the above example, we can see that if too much data is initialized through the constructor, it will result in a large number of overloaded constructors that are not easy to read. This code is not elegant, so we can use the builder pattern to optimize. The implementation of the Builder pattern is also relatively simple, creating an inner class of the builder on the object.

public class Share {
    private final String pic;
    private final String text;
    private final String url;
    private final String video;

    public Share(Builder builder){
        this.pic=builder.pic;
        this.text=builder.text;
        this.url=builder.url;
        this.video=builder.video;
    }
    public static class Builder{
        private String pic;
        private String text;
        private String url;
        private String video;

        public Builder setPic(String pic) {
            this.pic = pic;
            return this;
        }
        public Builder setText(String text) {
            this.text = text;
            return this;
        }
        public Builder setUrl(String url) {
            this.url = url;
            return this;
        }        
        public Computer build(a){
            return new Computer(this); }}// omit the getter method... }Copy the code

Summary of Usage Scenarios

  1. The builder can be used when an object has different combinations within it due to changes in the scene, as in the example above, where there are various combinations of types of sharing depending on the scene.
  2. Need to make sure that this object’s internal combination to initialization in a certain order, you can also use the builders, which is why most of the time must need to use the constructor for object initialization, not initialized by the method of the set of data, because the call set methods can’t guarantee the calls to order, with construction mode, We can both initialize the data through the set function and ensure that the data is initialized in the order.

Android application instance – AlertDialog

The Builder mode is used by an AlertDialog. The Builder mode is used by an AlertDialog

/frameworks/base/core/java/android/app/AlertDialog.java

public static class Builder {
    private final AlertController.AlertParams P;

    public Builder(Context context) {
        this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
    }

    public Builder(Context context, int themeResId) {
        P = new AlertController.AlertParams(new ContextThemeWrapper(
            context, resolveDialogTheme(context, themeResId)));
    }

    public Context getContext(a) {
        return P.mContext;
    }

    public Builder setTitle(@StringRes int titleId) {
        P.mTitle = P.mContext.getText(titleId);
        return this;
    }


    public Builder setTitle(CharSequence title) {
        P.mTitle = title;
        return this;
    }

    public Builder setMessage(@StringRes int messageId) {
        P.mMessage = P.mContext.getText(messageId);
        return this;
    }


    public Builder setMessage(CharSequence message) {
        P.mMessage = message;
        return this;
    }


    public Builder setIcon(@DrawableRes int iconId) {
        P.mIconId = iconId;
        return this;
    }


    public Builder setIcon(Drawable icon) {
        P.mIcon = icon;
        return this;
    }

    // omit the rest of the set methods...public Builder setView(int layoutResId) {
        P.mView = null;
        P.mViewLayoutResId = layoutResId;
        P.mViewSpacingSpecified = false;
        return this;
    }


    public Builder setView(View view) {
        P.mView = view;
        P.mViewLayoutResId = 0;
        P.mViewSpacingSpecified = false;
        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);
        dialog.setCancelable(P.mCancelable);
        if (P.mCancelable) {
            dialog.setCanceledOnTouchOutside(true);
        }
        dialog.setOnCancelListener(P.mOnCancelListener);
        dialog.setOnDismissListener(P.mOnDismissListener);
        if(P.mOnKeyListener ! =null) {
            dialog.setOnKeyListener(P.mOnKeyListener);
        }
        return dialog;
    }

    public AlertDialog show(a) {
        final AlertDialog dialog = create();
        dialog.show();
        returndialog; }}Copy the code

As you can see inside the Builder there are a number of set methods that set the properties of the Dialog. Each set method returns the value this, but unlike the previous example, the properties set by the AlertDialog through the Builder, All are encapsulated in the AlertController. AlertParams, do it is to meet the single responsibility principle, but AlertController. AlertParams can let other Dialog Builder reuse. When an AlertDialog is initialized with the Builder’s set, then the show() function is called, a dialog will appear.

We went on to look at the create () function call AlertController. AlertParams of the apply function Dialog initialization process.

/frameworks/base/core/java/com/android/internal/app/AlertController.java

public void apply(AlertController dialog) {
    if(mCustomTitleView ! =null) {
        dialog.setCustomTitle(mCustomTitleView);
    } else {
        if(mTitle ! =null) {
            dialog.setTitle(mTitle);
        }
        if(mIcon ! =null) {
            dialog.setIcon(mIcon);
        }
        if(mIconId ! =0) {
            dialog.setIcon(mIconId);
        }
        if(mIconAttrId ! =0) { dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId)); }}if(mMessage ! =null) {
        dialog.setMessage(mMessage);
    }
    if(mPositiveButtonText ! =null) {
        dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                         mPositiveButtonListener, null);
    }
    if(mNegativeButtonText ! =null) {
        dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
                         mNegativeButtonListener, null);
    }
    if(mNeutralButtonText ! =null) {
        dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
                         mNeutralButtonListener, null);
    }
    if (mForceInverseBackground) {
        dialog.setInverseBackgroundForced(true);
    }

    if((mItems ! =null) || (mCursor ! =null) || (mAdapter ! =null)) {
        createListView(dialog);
    }
    if(mView ! =null) {
        if (mViewSpacingSpecified) {
            dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                           mViewSpacingBottom);
        } else{ dialog.setView(mView); }}else if(mViewLayoutResId ! =0) { dialog.setView(mViewLayoutResId); }}Copy the code

As you can see, a complex AlertDialog object is now created through the Builder.

The singleton

Singletons come last in the modeling design pattern, because this is a design pattern that we use so often that any developer should be familiar with it, so I won’t talk about how to use it here, but I’ll talk about how to use singletons in multi-threading or single-threading.

Singletons are mainly divided into lazy singletons and hungry singletons. Lazy singletons initialize instances only when they are used, and hungry singletons initialize instances in advance when objects are declared or in static code blocks.

LanHanShi

Let’s look at lazy singletons written in non-thread-safe form

public class SingletonLazy {
    private static SingletonLazy mInstance;

    private SingletonLazy(a) {}public static SingletonLazy getInstanceUnLocked(a) {
        if (mInstance == null) {
            mInstance = new SingletonLazy();
        }
        returnmInstance; }}Copy the code

Look at lazy ways to keep thread-safe writing

public class SingletonLazy {
    private volatile static SingletonLazy mInstance;

    private SingletonLazy(a) {}public static SingletonLazy getInstance(a) {
        if (mInstance == null) {// First check
            synchronized (SingletonLazy.class) {/ / lock
                if (mInstance == null) {// Second check
                    mInstance = new SingletonLazy();// New an object}}}returnmInstance; }}Copy the code

Lazy singletons need to be double-checked to ensure singletons, and mInstance statements need to be declared with voliate. Why this needs to be done involves the knowledge of multi-threading, which will take a long time to complete. Please refer to my article “Mastering the Principles of Android and Java Threads”.

The hungry type

Lazy singletons are written as follows:

public class SingletonHungry {

    private static SingletonHungry mInstance = new SingletonHungry();

    private SingletonHungry(a) {}public static SingletonHungry getInstance(a) {
        returnmInstance; }}Copy the code

Lazy style is thread-safe. LanHanShi and hungry, which one is not better, but according to the business scenario, select the most appropriate, if use singleton scene is not multithreaded, it is ok to use LanHanShi not thread-safe, this kind of writing code is concise and easy to understand, if you need to ensure the safety of the thread, to figure out whether the object must be used, if must use to, So hungry will do, if not, then lazy thread-safe writing.

Android instance scenario — ProcessState

All processes in The Android system are fork by Zygote. After fork, onZygoteInit callback is executed. Binder is opened and initialized by calling ProcessState, which is a singleton.

Let’s look at the implementation of the onZygoteInit function.

/frameworks/base/cmds/app_process/app_main.cpp

virtual void onZygoteInit(a) {
    sp<ProcessState> proc = ProcessState::self(a); proc->startThreadPool(a); }Copy the code

Let’s look at what we’re doing in the ProcessState::self() function

/frameworks/native/libs/binder/ProcessState.cpp

sp<ProcessState> ProcessState::self(a)
{
    Mutex::Autolock _l(gProcessMutex);
    if(gProcess ! =NULL) {
        return gProcess;
    }
    gProcess = new ProcessState("/dev/binder");
    return gProcess;
}
Copy the code

As you can see, ProcessState is a non-thread-safe lazy singleton. Binder creation and communication principles are not covered in detail here. If you are interested, check out my article “Understanding Binder Communication Principles in Depth.”

structured

Understand creational design patterns, have a little sense to design patterns, creational design patterns is relatively simple, only need we in the new one object, consider whether the current scenario type can be used to create a design pattern for improvement and optimization, by contrast, structured design pattern is advanced, He requires us to combine multiple objects, organize them into a larger structure, and obtain new functions. The structural design mode mainly includes agent, adapter, bridge, decoration, appearance, share element, and combination. The following is a detailed understanding.

Agents and decorators

I put agents and decorators together because the two patterns are similar, and you can get confused when you’re new to both design patterns.

Let’s talk about the proxy model. What is the proxy model? A Client object wants to access object A. If A proxy is used, the Client cannot access object A directly, but accesses object P, the proxy object of object A. In this case, the Client directly accesses object B, and then object B accesses object A. You only need to access the B object to access the full functionality of the A object.

The proxy model is often used in Android application development. What are the benefits of using proxies? First, for example, the author do performance analysis for a project, need to know some method is time consuming, even though I can directly in these methods, but a violation of the single responsibility principle, and some methods are third party SDK provides methods of object, I can’t modify the source code directly, this time, you can use the proxy pattern, One of the primary functions of the proxy pattern is to extend the functionality of the proxied object. Here I continue with sharing as an example of how to use proxy mode to count method time in a share object.

  1. Creates an interface for the object being accessed
public interface Share{
    void shareImg(a);
    void shareText(a);
    void shareUrl(a);
}
Copy the code
  1. Create the entity class for the interface
public class QQShare implements Share{
    
    @Override
    public void shareImg(a){
        //do something
    };
    
    @Override
    public void shareText(a){
        //do something
    };
    
    @Override
    public void shareUrl(a){
        //do something
    };
}
Copy the code
  1. If I want to share each method is time consuming, though in QQShare to this object can be directly inserted into the time-consuming statistical code, but this is not standard, the statistical function does not belong to the ability to share responsibilities, these write the code high invasive, if QQShare class is encapsulated within the SDK, didn’t also the way in which we insert the code, This is where the proxy pattern comes in handy. Next, create a proxy object that implements the proxied object interface
public class QQShareProxy implements Share{
   private Share qqShare;
 
   public ShareProxy(a){
       // Create an instance of the proxied object inside the proxied object
      qqShare = new QQShare();
   }
 
    @Override
    public void shareImg(a){
        long startTime = System.currentTime;
        qqShare.shareImg();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
    
    @Override
    public void shareText(a){
        long startTime = System.currentTime;
        qqShare.shareText();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
    
    @Override
    public void shareUrl(a){
        long startTime = System.currentTime;
        qqShare.shareUrl();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
}
Copy the code

At this point, we can directly use QQShareProxy to count the time consuming of each method. When we use QQShareProxy, we have no perception of QQShare, because the corresponding QQShare instance will be created inside the Proxy.

The function of decorator mode is also to extend the function of the object, so we can also use decorator to extend the time statistics for the QQShare object, so what is the difference between it and the proxy mode? The main difference is that the proxy mode is unaware of the propped object. We do not need to know that the propped object exists, but the decorator is aware of the decorated object. Let’s look at the implementation of the decorator pattern in code.

public class ShareDecorate implements Share{
   private Share share;
 
   public ShareDecorate(Share share){
      this.share = share;
   }
 
    @Override
    public void shareImg(a){
        long startTime = System.currentTime;
        qqShare.shareImg();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
    
    @Override
    public void shareText(a){
        long startTime = System.currentTime;
        qqShare.shareText();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
    
    @Override
    public void shareUrl(a){
        long startTime = System.currentTime;
        qqShare.shareUrl();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
}
Copy the code

As you can see, in the constructor of the decorator pattern, we need to pass in the decorated object. The advantage of this is that we can not only extend the time statistics capability for the QQShare instance, but also pass in other shared objects, and extend the statistics capability for all other shared objects, such as WXShare, WBShare, etc. However, if you use proxy mode, there is no way, because a proxy mode, can only be a unique proxy object.

Summary of Usage Scenarios

After looking at the examples above, let’s summarize the usage scenarios of these two patterns.

The proxy pattern
  1. As in the example above, the proxy pattern extends the functionality of an object
  2. Proxy mode can also be used to authenticate access
Decorator mode
  1. The main purpose of decorators is to extend the capabilities of objects

Android Instance scenario – Binder

When we call startActivity to start an Activity, we are actually calling the AMS Proxy. By using the startActivity method of the AMS Proxy, we are starting an Activity without knowing that AMS exists. Let’s look at the process of starting a Activiyt by proxy.

The startActivity function will eventually call the startActivityForResult function

/frameworks/base/core/java/android/app/Activity.java

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
                                   @Nullable Bundle options) {
    if (mParent == null) {
        options = transferSpringboardActivityOptions(options);
        / / start the activity
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
            this, mMainThread.getApplicationThread(), mToken, this,
            intent, requestCode, options);
        if(ar ! =null) {
            mMainThread.sendActivityResult(
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                ar.getResultData());
        }
        if (requestCode >= 0) {
            mStartedActivity = true;
        }

        cancelInputsAndStartExitTransition(options);
    } else{... }}Copy the code

The startActivityForResult function actually calls the execStartActivity method of the Instrumentation object.

/frameworks/base/core/java/android/app/Instrumentation.java

public ActivityResult execStartActivity(
    Context who, IBinder contextThread, IBinder token, Activity target,
    Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; Uri referrer = target ! =null ? target.onProvideReferrer() : null;
    if(referrer ! =null) { intent.putExtra(Intent.EXTRA_REFERRER, referrer); }...try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        intresult = ActivityManager.getService() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target ! =null ? target.mEmbeddedID : null,
                           requestCode, 0.null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}
Copy the code

ExecStartActivity function key code is ActivityManager. GetService () startActivity, through this line of code, you can call AMS start-up activity, this line of code consists of three parts.

  1. Obtain the Binder addresses of the ServiceManager and create proxies for the ServiceManager application process based on the obtained Binder addresses
  2. Obtain the Binder address of ActivityManagerService and create a Proxy for AMS in the application process based on the Binder address of AMS
  3. Notifies ActivityManagerService to start the activity.

As the above process is designed into the communication process of Binder, it is a long process to explain Binder in detail. If you want to have a deeper understanding of Binder, you can read my article “Understanding The Principles of Binder”. Here I directly talk about AMS Proxy in user process. It is automatically generated when compiled by the IActivityManager AIDL file

/frameworks/base/core/java/android/app/IActivityManager.aidl

interface IActivityManager {...int startActivity(in IApplicationThread caller, in String callingPackage, in Intent intent,
                      in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode,
                          int flags, in ProfilerInfo profilerInfo, in Bundle options); ... }Copy the code

The generated proxy file is as follows

public interface IActivityManager extends android.os.IInterface {
    /** * Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements android.app.IActivityManager {
        private static final java.lang.String DESCRIPTOR = "android.app.IActivityManager";

        /** * Construct the stub at attach it to the interface. */
        public Stub(a) {
            this.attachInterface(this, DESCRIPTOR);
        }

        /** * Cast an IBinder object into an android.app.IActivityManager interface, * generating a proxy if needed. */
        public static android.app.IActivityManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if(((iin ! =null) && (iin instanceof android.app.IActivityManager))) {
                return ((android.app.IActivityManager) iin);
            }
            return new android.app.IActivityManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder(a) {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                caseINTERFACE_TRANSACTION:...caseTRANSACTION_openContentUri:...caseTRANSACTION_handleApplicationCrash:...case TRANSACTION_startActivity: {
                    data.enforceInterface(DESCRIPTOR);
                    android.app.IApplicationThread _arg0;
                    _arg0 = android.app.IApplicationThread.Stub.asInterface(data.readStrongBinder());
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    android.content.Intent _arg2;
                    if ((0! = data.readInt())) { _arg2 = android.content.Intent.CREATOR.createFromParcel(data); }else {
                        _arg2 = null;
                    }
                    java.lang.String _arg3;
                    _arg3 = data.readString();
                    android.os.IBinder _arg4;
                    _arg4 = data.readStrongBinder();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    int _arg6;
                    _arg6 = data.readInt();
                    int _arg7;
                    _arg7 = data.readInt();
                    android.app.ProfilerInfo _arg8;
                    if ((0! = data.readInt())) { _arg8 = android.app.ProfilerInfo.CREATOR.createFromParcel(data); }else {
                        _arg8 = null;
                    }
                    android.os.Bundle _arg9;
                    if ((0! = data.readInt())) { _arg9 = android.os.Bundle.CREATOR.createFromParcel(data); }else {
                        _arg9 = null;
                    }
                    int _result = this.startActivity(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6, _arg7, _arg8, _arg9);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }

                private static class Proxy implements android.app.IActivityManager {
                    private android.os.IBinder mRemote;

                    Proxy(android.os.IBinder remote) {
                        mRemote = remote;
                    }

                    @Override
                    public int startActivity(android.app.IApplicationThread caller, java.lang.String callingPackage, android.content.Intent intent, java.lang.String resolvedType, android.os.IBinder resultTo, java.lang.String resultWho, int requestCode, int flags, android.app.ProfilerInfo profilerInfo, android.os.Bundle options) throws android.os.RemoteException {
                        android.os.Parcel _data = android.os.Parcel.obtain();
                        android.os.Parcel _reply = android.os.Parcel.obtain();
                        int _result;
                        try{ _data.writeInterfaceToken(DESCRIPTOR); _data.writeStrongBinder((((caller ! =null))? (caller.asBinder()) : (null)));
                            _data.writeString(callingPackage);
                            if((intent ! =null)) {
                                _data.writeInt(1);
                                intent.writeToParcel(_data, 0);
                            } else {
                                _data.writeInt(0);
                            }
                            _data.writeString(resolvedType);
                            _data.writeStrongBinder(resultTo);
                            _data.writeString(resultWho);
                            _data.writeInt(requestCode);
                            _data.writeInt(flags);
                            if((profilerInfo ! =null)) {
                                _data.writeInt(1);
                                profilerInfo.writeToParcel(_data, 0);
                            } else {
                                _data.writeInt(0);
                            }
                            if((options ! =null)) {
                                _data.writeInt(1);
                                options.writeToParcel(_data, 0);
                            } else {
                                _data.writeInt(0);
                            }
                            mRemote.transact(Stub.TRANSACTION_startActivity, _data, _reply, 0);
                            _reply.readException();
                            _result = _reply.readInt();
                        } finally {
                            _reply.recycle();
                            _data.recycle();
                        }
                        return _result;
                    }
                }
            }
        }
    }
}
Copy the code

As you can see, the inner class Proxy is the AMS Proxy object

private static class Proxy implements android.app.IActivityManager {
    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }

    @Override
    public int startActivity(android.app.IApplicationThread caller, java.lang.String callingPackage, android.content.Intent intent, java.lang.String resolvedType, android.os.IBinder resultTo, java.lang.String resultWho, int requestCode, int flags, android.app.ProfilerInfo profilerInfo, android.os.Bundle options) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try{ _data.writeInterfaceToken(DESCRIPTOR); _data.writeStrongBinder((((caller ! =null))? (caller.asBinder()) : (null)));
            _data.writeString(callingPackage);
            if((intent ! =null)) {
                _data.writeInt(1);
                intent.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            _data.writeString(resolvedType);
            _data.writeStrongBinder(resultTo);
            _data.writeString(resultWho);
            _data.writeInt(requestCode);
            _data.writeInt(flags);
            if((profilerInfo ! =null)) {
                _data.writeInt(1);
                profilerInfo.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            if((options ! =null)) {
                _data.writeInt(1);
                options.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(Stub.TRANSACTION_startActivity, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readInt();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return_result; }}Copy the code

Calling the Proxy startActivty method is equivalent to calling the AMS startActivity method, because the Proxy will eventually call the AMS startActivity method through Binder.

appearance

The appearance pattern is relatively simple, that is, when we want to deal with multiple objects, we can encapsulate multiple classes into a class, this class is called the appearance, then we only need to deal with this class.

Or share for example, as has been mentioned earlier in sharing scenario, we need to deal with multiple Shared objects, such as QQ share, WeChat sharing, weibo share and so on, more cumbersome, use pattern appearance, to share all wrapped in a class, so we only need to use an object that is calling all drive sharing ability, pseudo code is as follows.

  1. Create the interface
public interface Share{
    void shareImg(a);
    void shareText(a);
    void shareUrl(a);
}
Copy the code
  1. Create the entity class for the interface
public class QQShare implements Share{
    
    @Override
    public void shareImg(a){
        //do something
    };
    
    @Override
    public void shareText(a){
        //do something
    };
    
    @Override
    public void shareUrl(a){
        //do something
    };
}

public class WXShare implements Share{
    
    @Override
    public void shareImg(a){
        //do something
    };
    
    @Override
    public void shareText(a){
        //do something
    };
    
    @Override
    public void shareUrl(a){
        //do something
    };
}

public class WBShare implements Share{
    
    @Override
    public void shareImg(a){
        //do something
    };
    
    @Override
    public void shareText(a){
        //do something
    };
    
    @Override
    public void shareUrl(a){
        //do something
    };
}
Copy the code
  1. Creating the appearance Class
public class ShareFacade{
    private Share qqShare;
    private Share wxShare;
    private Share wbShare;
    
    public ShareFacade(a){
        qqShare = new QQShare();
        wxShare = new WXShare();
        wbShare = new WBShare();
    }

    public void shareQQImg(a){
        qqShare.shareImg();
    };
    
    public void shareQQText(a){
        qqShare.shareText();
    };
    
    public void shareQQUrl(a){
        qqShare.shareUrl();
    };
    
    public void shareWXImg(a){
        wxShare.shareImg();
    };
    
    public void shareWXText(a){
        wxShare.shareText();
    };   

    public void shareWXUrl(a){
        wxShare.shareUrl();
    };
    
    public void shareWBImg(a){
        wbShare.shareImg();
    };

    public void shareWBText(a){
        wbShare.shareText();
    };   

    public void shareWBUrl(a){
       wbShare.shareUrl();
    };    
}
Copy the code

Android application instance — Context

Context is used a lot in Android, why? Context encapsulates many services of the Framework layer, which can be called directly. For example, startActivity can be started by calling startService, and Service can be started by calling startService. Context encapsulates various object instances, such as ActivityManagerService, PackageManager, AssetManager, etc., which is the essence of the facade pattern.

Let’s look directly at the source of Context, which is an abstract class

/frameworks/base/core/java/android/content/Context.java

public abstract class Context {
    public abstract AssetManager getAssets(a);
    public abstract Resources getResources(a);
    public abstract PackageManager getPackageManager(a);
    public abstract ContentResolver getContentResolver(a);
    public abstract Looper getMainLooper(a); ... }Copy the code

As you can see, the Context encapsulates various methods to get an object, and all the get methods are implemented in ContextWrapper, a subclass of Context, so we can just call the get method and get the object we want to deal with.

Summary of Usage Scenarios

  1. Appearance patterns come in handy when a system is very complex and has many objects. Appearance patterns are mainly used to provide external access to complex modules or subsystems.

The adapter

The name from the adapter, we can know that it is the role of the adaptation of two incompatible things, the most familiar example is the us plug, if we go to foreign travel, domestic plug is not used directly, so we need a plug converter, convert our plug into compatible plug abroad. In the same way, the adapter pattern translates the interface of one class into another interface that the customer expects, allowing two classes to work together that would otherwise not work together due to interface incompatibility.

The following is an example. The author once developed a navigation function, inputting two locations, obtaining latitude and longitude, and then finding the best route. At the beginning, I used the default map interface of the mobile phone, and the pseudo-code is as follows.

  1. Create the default map interface
public interface IMap{
    // Get longitude
    public float getLatitude(String location);
    // Get the latitude
    public float getLongitude(String location);
}
Copy the code
  1. The system has its own native map to achieve the interface
public class DefaultMap implements IMap{
    @Override
    public float getLatitude(String location){
		// Get longitude from place names... }@Override
    public float getLongitude(String location){
        // Get latitude from place names... }}Copy the code

3. Create an object that invokes map capabilities for route planning

public class MapUtil{
    private IMap map;
    public MapUtil(IMap map){
        this.map = map;
    }
    
    public void linePlan(String locationA,String locationB){
        flong latA = map.getLatitude();
        float lngA = map.getLongitude();
        flong latB = map.getLatitude();
        float lngB = map.getLongitude();
        // Get the latitude and longitude of the two places... }}Copy the code

At this point, the Client can use MapUtil to implement the function of route planning

public class Client {
   public static void main(String[] args) {
       MapUtil mapUtil = new MapUtil(new DefaultMap());
       mapUtil.linePlan("Home"."The company"); }}Copy the code

After a period of time, because the mobile phone positioning are not allowed to own map often happen, so I need to baidu map, this time problem, because baidu map is not inherited from IMap, MapUtil constructor needs to IMap, so the code above is not available, due to the SDK baidu map is a third party, You can’t modify the source code, so you have to modify MapUtil, but we know that one of the principles of code design is to turn off changes, open extensions, and modify MapUtil directly. This is where the adapter pattern comes in.

At this point we just need to design an adapter for Baidu map

public class MapAdapter implements IMap {
 
   BaiduMap map;
 
   public MediaAdapter(BaiduMap map){
      this.map = map;
   }
 
   @Override
    public float getLatitude(String location){
		// Get longitude from place names
        return map.getLat();
    }
    
    @Override
    public float getLongitude(String location){
        // Get latitude from place names
        returnmap.getLng(); }}Copy the code

With this adapter, we can use Baidu maps without modifying MapUtil, and if we want to continue to use other third-party maps, such as Autonavi, we just need to expand the adapter.

public class Client {
   public static void main(String[] args) {
       MapUtil mapUtil = new MapUtil(new MapAdapter(new BaiduMap());
       mapUtil.linePlan("Home"."The company"); }}Copy the code

Summary of Usage Scenarios

  1. The main usage scenario of the adapter is compatible with objects with incompatible interfaces. At the beginning of the code development, we cannot use the adapter mode. Only after the code development is completed, or when the SDK of a third party is used, we find incompatible interfaces.

Android application instance — Adapter

There are many places in Android that use the Adapter pattern. Here we use the ListView Adapter as an example. Why do we need to use an Adapter here? The ListView component we use is the Android system has been developed at the beginning of the design, just like the foreign three hole row plug, at the beginning of the design can only use the designated three hole plug, ListView is the same, at the beginning of the design it has a fixed use method, such as must set the style of each Item, Set the number of total items, and display effects have fixed usage.

As developers on the Android system, we have no way or very complicated to display our own View on the ListView control. For example, we can not directly use the foreign three-hole plug when holding the domestic two-hole plug. An Adapter is an Adapter that displays our own View on the ListView.

The Adapter defines the use method, we just need to follow the Adapter defined method, fill our own View and implement the response method, and then set the Adpater to the ListView, we can perfectly adapt our own View to the ListView.

public class MyAdapter extends BaseAdapter{

    private LayoutInflater mInflater;
    List<String> mDatas ; 

    public MyAdapter(Context context, List<String> datas){
        this.mInflater = LayoutInflater.from(context);
        mDatas = datas ;
    }
    @Override
    public int getCount(a) {
        return mDatas.size();
    }

    @Override
    public String getItem(int pos) {
        return mDatas.get(pos);
    }

    @Override
    public long getItemId(int pos) {
        return pos;
    }

    // Parse, set, cache convertView, and related content
    @Override
    public View getView(int position, View convertView, ViewGroup parent) { 
        ViewHolder holder = null;
        // Item View reuse
        if (convertView == null) {
            holder = new ViewHolder();  
            convertView = mInflater.inflate(R.layout.my_listview_item, null);
            / / get the title
            holder.title = (TextView)convertView.findViewById(R.id.title);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder)convertView.getTag();
        }
        holder.title.setText(mDatas.get(position));
        returnconvertView; }}Copy the code

In fact, if we want to develop the SDK available to other developers or development platform, the adapter is the most common, because we can’t foresee this SDK or use our system developers need to what kind of object, so we need to design good adapter, so no matter what objects they want to, Can be adapted to our own systems using adapters.

The bridge

The purpose of the bridge pattern is to decouple the abstraction of an object’s interface from its implementation. This concept is a little difficult to understand. In common terms, we define the abstraction of an object’s interface, but the implementation of this interface is another object.

The bridge mode is illustrated here using the artboard feature mentioned earlier in the prototype section. Drawing board can draw all kinds of shapes, circle, rectangle, diamond and so on, these shapes have a variety of colors, white, black, red and so on. As shown in the figure.

The bridge pattern comes in handy if we create an object in the shape of a property that extends to a large number of classes. Here’s how to use it.

  1. Create an abstract class for Shape
public abstract class Shape {
   protected Color color;
   protected Shape(Color color){
      this.color = color;
   }
   public abstract void draw(a);  
}
Copy the code
  1. Create color interface
public interface ColorDraw {
   public void draw(String type);
}
Copy the code
  1. Implement Shape abstract class entity
public class Circle extends Shape {
   private ColorDraw colorDraw;
 
   public Circle(ColorDraw colorDraw) {
      super(colorDraw);
      this.colorDraw = colorDraw;
   }
 
   @Override
   public void draw(a) {
      colorDraw.draw("Circle"); }}public class Rectangle extends Shape {
   private ColorDraw colorDraw;
 
   public Rectangle(ColorDraw colorDraw) {
      super(colorDraw);
      this.colorDraw = colorDraw;
   }
 
   @Override
   public void draw(a) {
      colorDraw.draw("Rectangle"); }}Copy the code
  1. Create an entity bridge implementation class for the ColorDraw interface
public class RedDraw implements ColorDraw {
    
   @Override
   public void draw(String type) {
      System.out.println("Drawing Green "+type); }}public class BlackDraw implements ColorDraw {
    
   @Override
   public void draw(String type) {
      System.out.println("Drawing black "+type); }}Copy the code

At this point, the bridge mode is involved. At this point, the client only needs to select a shape and match a color to get the desired shape, instead of directly integrating the color drawing function and shape into an object.

Shape shape = new Circle(new RedDraw())
shape.darw();
Copy the code

Using the above example, we looked at the concept of the bridge pattern: the bridge pattern is to separate the abstract parts from the implementation parts, so that they can change independently, is not easier to understand?

Summary of Usage Scenarios

The following are the usage scenarios of the bridge mode

  • If there are two or more independent dimension changes in a class, and these dimensions need to be expanded, the bridge mode can be used to achieve decoupling and composition.
  • When you don’t want to use inheritance or when the number of classes increases dramatically because of multi-level inheritance.
  • If a system needs to add more flexibility between the abstract and embodied roles of the component, avoiding static inheritance between the two levels, they can be made to establish an association at the abstraction level through the bridge pattern.

Android application instance — View

There are many subclasses of View, TextView, Button, ListView… These views can also be drawn in a variety of ways, such as Skia, OpenGl, Vulkan, etc. If we do not talk about the implementation of View drawing and definition separation, as in the above example, it will be extended to a very large number of objects.

In the process of drawing, View actually encapsulates its implementation into a Canvas object. Skia draws the Canvas of Skia, and OpenGl draws the Canvas corresponding to OpenGl. Since drawing is too complicated, I won’t expand it in detail here. If you are interested, check out my article “Mastering the Principles of Android Image Display.”

combination

The use of composite mode is very limited, generally only used when building Dom trees, View trees, so I will directly explain the use of composite mode in Android scenarios: ViewGroup, let you know about this mode, because when you need to use the composite mode, it is natural to think of the composite mode, otherwise you may not be able to complete the function development of the scene.

Android application instance — ViewGroup

Let’s look at the use of a composite pattern

  1. Create the base element object
Public class View {... }Copy the code
  1. Creates a container object that inherits from the base element
public abstract class ViewGroup extends View{
    
    // Add an attempt
    public void addView( View view){
        ……
    }
    
    // Delete a view
    public void removeView( View view) {}// Get a child View
    public View getChildAt(int index) {... }}Copy the code

These two steps are the use of the combined mode, which is mainly used in the system with tree structure. By using the combined mode, we can freely increase the nodes of the system, and simplify the call of the high-level module to the low-level module. For example, the ViewGroup is a tree structure, and the ViewGroup has sub-views. Child views can also have child Views, and we can access a child View with getChildAt, which simplifies calls from higher-level modules to lower-level modules.

The flyweight

Share element mode is a very simple mode, generally speaking, is to cache the object, when using directly, do not need to create, improve performance, such as our most commonly used LruCache, can also be understood as share element, because this design mode is too simple and too common, so here will not expand to say.

At the end

That concludes the twelve design patterns for creation and structure, leaving the eleven for behavior, which I will cover in the next article.

Read this article, perhaps someone will say I speak many patterns and other places of the implementation of the way, I can only say that is indeed there will be some difference, such as many local builder patterns, there will be a director, such as Android example scenario example in the article, it looks as if did not strictly according to its corresponding design pattern is used, In fact, the use of design patterns is not necessarily implemented in strict accordance with the GOF specification. We can also see from the Android source code that the use of various design patterns is not strictly in accordance with the GOF UML specification. The specific implementation of design patterns is not absolute. Key or understanding model contains the ideas to solve the problem, we don’t need to remember the UML class diagram design pattern, don’t need to remember to use some design patterns of step 1, step 2, but we need to understand why use, what are the benefits of such use, and what kind of scene, with the most elegant what kind of way to solve the problem.