Talk about the application of 23 design patterns in Android projects

Talk about the application of 23 design patterns in Android projects

Preface

This article will discuss 23 design patterns in combination with actual practice. Each design pattern involves

  • Definition: Abstract definition and popular description, try to make its meaning and application scenarios clear
  • Example: If the pattern has been used in the project, the code in the project will be given, otherwise the Java code that is as simple and easy to understand as possible will be given
  • Android: Where is this design pattern used in the Android source code framework?
  • Refactoring: Is there any place in the project that can be refactored using this pattern? If so, give the code or ideas before and after the refactoring.

Introducing design patterns in this way aims to help you better understand design patterns and get closer to them by combining them with the actual Android project development you encounter every day. At the same time, in actual development and refactoring, you can think about the refactoring methods and design patterns that can be applied, which can not only ensure that you write more reusable and reliable code, but also provide the best practices and summary of how to use the two pillars of refactoring and design patterns for elegant programming.

At the same time, introducing 23 design patterns at one time in this way is also out of the idea that if you want to use a pattern, you should first know such a pattern. The Gang of Four's "Design Patterns" is also a summary of experience, but if there is a giant supporting you up, why bother to build a ladder in the dark?

Refactoring is not the focus of this chapter, because this is also a very large topic. Here we only discuss whether there are some areas in the actual project that can be improved using design patterns.
Regarding refactoring, I have also written a blog post "Refactoring: Improving the Design of Existing Code", which basically lists the key points in "Refactoring: Improving the Design of Existing Code". In the future, I will continue to apply the techniques and design patterns in "Refactoring" to actual projects. After summarizing, I will write a few more blog posts on practical applications.

Introduction

Design pattern is a set of repeatedly used, well-known, catalogued, and summarized code design experiences. Design patterns are used to reuse code, make it easier for others to understand, and ensure code reliability. There is no doubt that design patterns are a win-win for oneself, others, and the system. Design patterns make code compilation truly engineering-oriented. Design patterns are the cornerstone of software engineering, just like the bricks and stones of a building. The reasonable use of design patterns in projects can perfectly solve many problems. Each pattern has a corresponding principle in the present. Each pattern describes a problem that keeps happening around us, as well as the core solution to the problem. This is why it can be widely used.

Six principles

Single Responsibility Principle

The single principle is simple, which is to encapsulate a group of highly related functions and data into one class. In other words, a class should have a single responsibility.

Open/Closed Principle

The open-closed principle is not complicated to understand. It means that a class should be open to extension but closed to modification. When writing code, you should try to implement new functions through extension rather than by modifying existing code. Otherwise, it is easy to destroy the original system and may also bring new problems. If you find that it cannot be implemented through extension, you should consider whether it is a problem in the code structure and solve it through refactoring.

Liskov Substitution Principle

All places that reference the base class must be able to transparently use its subclass objects. Essentially, this means making good use of inheritance and polymorphism, declaring variables (or parameters) in the form of the parent class, and assigning variables (or parameters) to any subclass that inherits from this parent class.

Dependency Inversion Principle

Dependency inversion is mainly about achieving decoupling, so that high-level modules do not depend on the specific implementation details of low-level modules. To understand it, we need to know several key points:

  • High-level modules should not depend on low-level modules (concrete implementations), both should depend on their abstractions (abstract classes or interfaces)
  • Abstractions should not depend on details
  • Details should depend on abstractions

In the Java language we use, abstraction refers to interfaces or abstract classes, both of which cannot be instantiated directly; details are implementation classes, and classes that implement interfaces or inherit abstract classes are details. Using Java language to describe it is: the parameters passed between modules are declared as abstract types, rather than as specific implementation classes;

Interface Segregation Principle

Dependencies between classes should be based on the smallest interface. The principle is to split very large and bloated interfaces into smaller and more specific interfaces.

Demeter Principle

An object should have minimal knowledge of other objects.

Suppose class A implements a certain function, and class B needs to call class A to execute this function, then class A should only expose one function to class B, which represents the function that implements this function, rather than letting class A expose all the subdivided functions that implement this function to B.

Design Patterns

Singleton Pattern

definition

Make sure that the singleton class has only one instance, and the singleton class provides a function interface for other classes to obtain this unique instance.
If a class consumes a lot of resources when it is created, that is, the cost of creating this class is very high; or if this class takes up a lot of memory, if too many instances of this class are created, it will cause too much memory usage. In the above cases, the singleton pattern should be used.

Practical Application

   // Singleton object
    private static AdvertPresenter mInstance;
    /** * Private constructor */
    private AdvertPresenter () {
    }
    /** * Get the AdvertPresenter instance * @return */
    public static AdvertPresenter getInstance () {
        if (mInstance == null ) {
            synchronized (AdvertPresenter.class) {
                if (mInstance == null ) {
                    mInstance = new AdvertPresenter();
                }
            }
        }
        return mInstance;
    }

Android

 //Get the WindowManager service reference
WindowManager wm = ( WindowManager )getSystemService(getApplication(). WINDOW_SERVICE );

Internally, it holds a WindowManager in a singleton manner and returns this object

Refactoring

There are multiple operations using Random and Gson in the project. You can encapsulate the Random and Gson objects into singletons for use.

Builder Pattern

definition

Separating the construction of a complex object from its representation allows the same construction process to create different representations.
Mainly, when creating an object, you need to set a lot of parameters (through setter methods), but these parameters must be set in a certain order, or different setting steps will produce different results.

Example

Various custom Dialogs

Android

AlertDialog.Builer builder= new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon)
    .setTitle( "title" )
    .setMessage( "message" )
    .setPositiveButton( "Button1" ,
        new DialogInterface.OnclickListener(){
            public void onClick (DialogInterface dialog, int whichButton) {
                setTitle( "click" );
            }   
        })
    .create()
    .show();

Refactoring

None

Prototype Pattern

definition

Use prototype instances to specify the type of object to be created, and create new objects by copying these prototypes.
The prototype mode can be used when a class has a lot of attributes but needs to be copied frequently. This way the code is concise and convenient.
Pay attention to shallow copy and deep copy when copying

Example

     private HashMap getClonePointMap(Map map) {
            HashMap clone = new HashMap<>();
            if (map != null ) {
                Iterator iterator = map.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry entry = (Map.Entry) iterator.next();
                    String key = (String) entry.getKey();
                    PointBean pointBean = (PointBean) entry.getValue();
                    if (pointBean != null ) {
                        //Traverse the map and put the cloned objects into the new map
                        clone .put(key, pointBean. clone ());
                    } else {
                        clone .put(key, null );
                    }
                }
            }
            return clone ;
        }

Android

Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
//Clone the copy
Intent copyIntent=(Intetn) shareIntent.clone ();

Refactoring

If you want to remove the parameter values ​​of an object one by one and assign them to another object, you can use the prototype mode.

Factory Pattern

Simple factory pattern

definition

Create a factory (a function or a class method) to make new objects.

Example

 public static Operation createOperate ( string operate ) {
    Operation oper = null ;
    switch (operate)
    {
        case "+" :
            {
            oper = new OperationAdd();
            break ;
            }
        case "-" :
            {
            oper = new OperationSub();
            break ;
            }
        case "*" :
            {
            oper = new OperationMul();
            break ;
            }
        case "/" :
            {
            oper = new OperationDiv();
            break ;
            }
    }
    return oper;
   }
}

Android

 public Object getSystemService (String name) {
    if (getBaseContext() == null ) {
        throw new IllegalStateException( "System services not available to Activities before onCreate()" );
    }
    //........
    if (WINDOW_SERVICE.equals(name)) {
         return mWindowManager;
    } else if (SEARCH_SERVICE.equals(name)) {
        ensureSearchManager();
        return mSearchManager;
    }
    //.......
    return super .getSystemService(name);
  }

The simple factory pattern is used in the getSystemService method. The decision on which object to create is based on the passed in parameters. Since these objects have been created in advance in singleton mode, there is no need to create a new one here. Just return the singleton directly.

Refactoring

 //Before reconstruction
public class AdvertPresenter {
    ...
    private void initAdvertManager () {
        String[] platforms = mAdConfig.getAllPlatforms();
        if (platforms != null && platforms.length > 0 ) {
            int platformSize = platforms.length;
            for ( int i = 0 ; i < platformSize; i++) {
                String platform = platforms[i];
                if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_FACEBOOK)) {
                    FacebookAdvertManager fbAdManager = new FacebookAdvertManager();
                    mAdvertManager.put(AdvertConstant.AD_PLATFORM_FACEBOOK, fbAdManager);
                } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_ADMOB)) {
                    AdMobAdvertManager adMobAdvertManager = new AdMobAdvertManager();
                    mAdvertManager.put(AdvertConstant.AD_PLATFORM_ADMOB, adMobAdvertManager);
                } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_MOPUB)) {
                    MopubAdvertManager mopubAdvertManager = new MopubAdvertManager();
                    mAdvertManager.put(AdvertConstant.AD_PLATFORM_MOPUB, mopubAdvertManager);
                } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_ADX)) {
                    AdxAdvertManager mopubAdvertManager = new AdxAdvertManager();
                    mAdvertManager.put(AdvertConstant.AD_PLATFORM_ADX, mopubAdvertManager);
                }
            }
        }
    }
    ...
}
//After reconstruction
public class BaseAdvertManager {
    ...
    public static BaseAdvertManager create (String platform) {
        if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_FACEBOOK)) {
            return new FacebookAdvertManager();
        } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_MOPUB)) {
            return new MopubAdvertManager();
        } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_ADX)) {
            return new AdxAdvertManager();
        } else if (platform.equalsIgnoreCase(AdvertConstant.AD_PLATFORM_ADMOB)) {
            return new AdMobAdvertManager();
        } else {
            *** return new NullAdvertManager();*** //Introduce NULL object
        }
    }
    ...
}
public class AdvertPresenter {
    ...
    private void initAdvertManager () {
        String[] platforms = mAdConfig.getAllPlatforms();
        if (platforms != null && platforms.length > 0 ) {
            int platformSize = platforms.length;
            for ( int i = 0 ; i < platformSize; i++) {
                String platform = platforms[i];
                mAdvertManager.put(platform, BaseAdvertManager.create(platform));
            }
        }
    }
    ...
}

Factory Method Pattern

definition

It is to define a factory interface for creating product objects, let its subclasses decide which class to instantiate, and defer the actual creation work to the subclasses.

Example

 public abstract class Product {
    public abstract void method () ;
}

public class ConcreteProduct extends Prodect {
    public void method () {
        System.out.println( "I am a specific product!" );
    }
}

public abstract class Factory {
    public abstract Product createProduct () ;
}

public class ConcreteFactory extends Factory {

    public Product createProduct () {
        return new ConcreteProductA();
    }
}

Android

We will use a lot of data structures in development, such as ArrayList, HashMap, etc. Let's first look at the brief UML diagram of the collection framework of the Collection part in Java.

We know that Iterator is an iterator, which is used to traverse the elements in a collection. Different data structures have different traversal methods, so the implementation of iterators is also different. Using the factory method pattern to defer the specific type of the iterator to the specific container class is more flexible and easy to expand.
 public interface Iterable {
    /** * Returns an iterator over elements of type { @code T}. * * @return an Iterator. */
    Iterator iterator () ;

    //Omit some code
}

List and Set inherit from Collection interface, which inherits from Iterable interface. So List and Set interface also need to inherit and implement the iterator() method in Iterable. Then the iterator method in two commonly used indirect implementation classes ArrayList and HashSet specifically constructs and returns an iterator object for us.
We find the ArrayList class and look at the implementation of the iterator method.

 @Override
public Iterator iterator () {
    return new ArrayListIterator();
}

The ArrayListIterator type is defined as follows:

 private class ArrayListIterator implements Iterator {
    /** Number of elements remaining in this iteration */
    private int remaining = size;

    /** Index of element that remove() would remove, or -1 if no such elt */
    private int removalIndex = - 1 ;

    /** The expected modCount value */
    private int expectedModCount = modCount;

    public boolean hasNext () {
        return remaining != 0 ;
    }

    @SuppressWarnings ( "unchecked" ) public E next () {
        ArrayList ourList = ArrayList.this ;
        int rem = remaining;
        if (ourList.modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        if (rem == 0 ) {
            throw new NoSuchElementException();
        }
        remaining = rem - 1 ;
        return (E) ourList.array[removalIndex = ourList.size - rem];
    }

    public void remove () {
        Object[] a = array;
        int removalIdx = removalIndex;
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        if (removalIdx < 0 ) {
            throw new IllegalStateException();
        }
        System.arraycopy(a, removalIdx + 1 , a, removalIdx, remaining);
        a[--size] = null ; // Prevent memory leak
        removalIndex = - 1 ;
        expectedModCount = ++modCount;
    }
}

We see that this class implements the Iterator interface, which is defined as follows:

 public interface Iterator {

    boolean hasNext () ;

    E next () ;

    default void remove () {
        throw new UnsupportedOperationException( "remove" );
    }

    default void forEachRemaining (Consumer action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

Now that the basic structure has been analyzed, let's take a look at how to implement the factory method pattern.
Iterator————>Product ArrayListIterator————>ConcreteProduct
Iterable/List————>Factory ArrayList————>ConcreteFactory
The factory method delays the instantiation of a class to a subclass, which corresponds to delaying the creation of an iterator from List to ArrayList. This is the factory method pattern.

Refactoring

None

Abstract Factory Pattern

definition

Provides an interface for creating a group of related or interdependent objects without specifying their concrete classes. The abstract factory pattern is a factory pattern used when there are multiple abstract roles. The abstract factory pattern can provide an interface to the client, allowing the client to create product objects in multiple product families without having to specify the specific products.

Example

 public abstract class AbstractProductA {
    public abstract void method () ;
}
public abstract class AbstractProdectB {
    public abstract void method () ;
}

public class ConcreteProductA1 extends AbstractProductA {
    public void method () {
        System.out.println( "Specific method of product A1!" );
    }
}
public class ConcreteProductA2 extends AbstractProductA {
    public void method () {
        System.out.println( "Specific method of product A2!" );
    }
}
public class ConcreteProductB1 extends AbstractProductB {
    public void method () {
        System.out.println( "Specific method of product B1!" );
    }
}
public class ConcreteProductB2 extends AbstractProductB {
    public void method () {
        System.out.println( "Specific method of product B2!" );
    }
}

public abstract class AbstractFactory {
    public abstractAbstractProductA createProductA () ;

    public abstractAbstractProductB createProductB () ;
}

public class ConcreteFactory1 extends AbstractFactory {
    public AbstractProductA createProductA () {
        return new ConcreteProductA1();
    }

    public AbstractProductB createProductB () {
        return new ConcreteProductB1();
    }
}

public class ConcreteFactory2 extends AbstractFactory {
    public AbstractProductA createProductA () {
        return new ConcreteProductA2();
    }

    public AbstractProductB createProductB () {
        return new ConcreteProductB2();
    }
}

Android

Due to the limitations of this mode, it is rarely used in Android. IPolicy in the com.android.internal.policy package uses this mode. It is an abstract factory for a series of window-related products such as Android windows, window management, layout loading, and event fallback handlers. However, there is only one specific factory implementation in the source code. Because this part has a relatively complex structure and a large amount of code, interested students can check the relevant information or read the source code by themselves.

Contrast with the Factory Method Pattern

use

  • Does not depend on the details of how product class instances are created, composed, and represented;
  • The product has more than one product family, and the system consumes only products from one family;
  • Products belonging to the same product family are used together;
  • Provide a library of product classes, all products appear with the same interface, so that users are not dependent on the implementation;

the difference

  • Abstract Factory is an upgrade to a factory method;
  • The abstract method provides a product family, that is, multiple product level structures, while the factory method is for a product level structure;
  • The products provided by the abstract method are derived from multiple abstractions or interfaces, while the factory method is derived from the same abstraction or interface;

advantage

  • The Abstract Factory pattern isolates the production of concrete classes so that clients do not need to know what is being created.
  • When multiple objects in a product family are designed to work together, it ensures that clients always use only objects in the same product family.
  • It is easy to add new specific factories and product families without modifying the existing system, in line with the "open-closed principle".

shortcoming

  • Adding a new product hierarchy is very complicated, requiring modifications to the abstract factory and all concrete factory classes, and is biased towards supporting the "open-closed principle".
  • (You can think of AB in the example as levels, 12 as groups, and A1B1 as different levels of the same group. This is convenient when adding new products at the same level, but adding products of different levels will destroy the "open-closed principle")

Because the abstract factory is not easy to expand new product families, this design pattern is rarely used when providing access to external personnel. Some people also say that the abstract factory method pattern is a very "disgusting" design pattern. The most typical application is the original purpose of this pattern, that is, to build a view family to adapt to the views under the two operating systems of Unit and Windows, and the view family has different implementations; the other is the object operation family formed by the operation of different databases in the operation of Java connecting to the database, but when the data is changed again, the modification of the interface required is also very troublesome, so the scalability is not good.

Refactoring

None

Strategy Pattern

definition

There is a series of algorithms, each algorithm is encapsulated (each algorithm can be encapsulated into a different class), and each algorithm can be replaced. The strategy pattern allows the algorithm to change independently of the clients who use it.

Example

 public abstract class BaseAdvertManager {
    protected abstract void doLoadAdvert () ;
}

public class FacebookAdvertManager extends BaseAdvertManager {
 @Override
    protected void doLoadAdvert () {
        Log.v(TAG, "Loading Facebook ads" );
    }
}

public class AdmobAdvertManager extends BaseAdvertManager {
 @Override
    protected void doLoadAdvert () {
        Log.v(TAG, "Load Admob ads" );
    }
}

Android

Android uses the strategy pattern when using time interpolators in property animation. When using animation, you can choose LinearInterpolator, AccelerateDecelerateInterpolator, DecelerateInterpolator, and custom interpolators. These interpolators all calculate the percentage of change in the current property value based on the percentage of time elapsed. By selecting different interpolators as needed, different animation effects can be achieved.

Refactoring

None

State Pattern

definition

In the state pattern, the behavior is determined by the state, and different states have different behaviors. The structure of the state pattern and the strategy pattern are almost the same, but the purpose and nature of their expression are different.

Example

 public interface TvState {
    public void nextChannerl () ;
    public void prevChannerl () ;
    public void turnUp () ;
    public void turnDown () ;
}

public class PowerOffState implements TvState {
    public void nextChannel () {}
    public void prevChannel () {}
    public void turnUp () {}
    public void turnDown () {}

}

public class PowerOnState implements TvState {
    public void nextChannel () {
        System.out.println( "Next channel" );
    }
    public void prevChannel () {
        System.out.println( "Previous channel" );
    }
    public void turnUp () {
        System.out.println( "Turn up the volume" );
    }
    public void turnDown () {
        System.out.println( "Turn down the volume" );
    }

}

public interface PowerController {
    public void powerOn () ;
    public void powerOff () ;
}

public class TvController implements PowerController {
    TvState mTvState;
    public void setTvState (TvStete tvState) {
        mTvState=tvState;
    }
    public void powerOn () {
        setTvState( new PowerOnState());
        System.out.println( "Starting up" );
    }
    public void powerOff () {
        setTvState( new PowerOffState());
        System.out.println( "Shutdown" );
    }
    public void nextChannel () {
        mTvState.nextChannel();
    }
    public void prevChannel () {
        mTvState.prevChannel();
    }
    public void turnUp () {
        mTvState.turnUp();
    }
    public void turnDown () {
        mTvState.turnDown();
    }
}

public class Client {
    public static void main (String[] args) {
        TvController tvController= new TvController();
        tvController.powerOn();
        tvController.nextChannel();
        tvController.turnUp();

        tvController.powerOff();
        //Turn up the volume, it will not take effect at this time
        tvController.turnUp();
    }
}

Android

The state mode is used in many places in the Android source code. For example, the Android WIFI management module. When WIFI is turned on, it automatically scans the surrounding access points and displays them in a list; when WIFI is turned off, it clears them. Here, the WIFI management module performs different actions according to different states.

Difference from Strategy Pattern

The behavior of the state pattern is parallel and irreplaceable, while the strategy pattern is an object behavior pattern whose behaviors are independent and replaceable.

Refactoring

There are switches for functions such as face slimming in the project. Now they are judged by configuration files, and can be reconstructed through state mode. Then, when processing pictures, the polymorphic characteristics can be used to directly use objects for processing.

Chain of Responsibility Pattern

definition

Give multiple objects the opportunity to process the request, thereby avoiding direct coupling between the sender and receiver of the request, connecting these objects into a chain, and passing the request along the chain until an object processes it.

Example

 /** * Abstract handler */
public abstract class Handler {

    /** * Holds the successor responsible object */
    protected Handler successor;
    /** * This is a method for processing requests. Although this method does not have any parameters passed in, * it is actually possible to pass parameters in. You can choose whether to pass parameters based on your specific needs. */
    public abstract void handleRequest () ;
    /** * Value retrieval method */
    public Handler getSuccessor () {
        return successor;
    }
    /** * Assignment method, set the subsequent responsible object */
    public void setSuccessor ( Handler successor ) {
        this .successor = successor;
    }

}

/** * Specific handler */
public class ConcreteHandler extends Handler {
    /** * Processing method, call this method to process the request */
    @ Override public void handleRequest () {
        /** * Determine whether there is a successor responsible object * If yes, forward the request to the successor responsible object * If not, process the request */
        if (getSuccessor() != null )
        {           
            System.out.println( "Return request" ) ;
            getSuccessor().handleRequest();           
        } else
        {           
            System.out.println( "Processing request" ) ;
        }
    }

}

/** * The client class that initiates the request */
public class Client {

    public static void main ( String[] args ) {
        //Assembly responsibility chain
        Handler handler1 = new ConcreteHandler();
        Handler handler2 = new ConcreteHandler();
        handler1.setSuccessor(handler2);
        //Submit the request
        handler1.handleRequest();
    }

}

Android

When Android processes a click event, the parent View receives the click event first. If the parent View does not process it, it is handed over to the child View, passing the responsibility down in sequence. Java's exception capture mechanism is also a manifestation of the chain of responsibility model.

Refactoring

None

Interpreter mode

definition

Given a language, define its grammar, and define an interpreter that is used to parse the language.

Example

For example, you can write configuration files for various functional modules, and then load the configuration files as configuration objects during operation according to the configuration file writing rules defined by the project. This mode should be used more or less in daily projects, so I will not post the code.

Android

This is used in many places. One of them is that the four major components of Android need to be defined in AndroidManifest.xml. In fact, AndroidManifest.xml defines the attributes of tags (statements) and their sub-tags, stipulates the specific use (grammar), and is parsed by PackageManagerService (interpreter).

Refactoring

None

Command Mode

definition

The command pattern encapsulates each request into an object, allowing users to parameterize the client with different requests; queue or log requests, and support undoable operations.
Let's take an example to understand: when we click the "Shutdown" command, the system will perform a series of operations, such as pausing event processing, saving system configuration, ending program processes, calling kernel commands to shut down the computer, etc. These commands are encapsulated from different objects and then put into the queue to be executed one by one, and undo operations can also be provided.

Example

   public void method () {
        Handler.post( new Runnable() {
            @Override
            public void run () {
                clearCache();
                statics();
                finish();
            }
        });
    }

Android

In the Android event mechanism, the underlying logic forwards and processes events. Each key event will be encapsulated into a NotifyKeyArgs object, and the specific event operation will be encapsulated through the InputDispatcher. Another example is the Runnable we use. We can use it to encapsulate the operations we want to do, and then hand them over to the Handler to process in sequence, or remove them before processing.

Refactoring

The current advertising module business logic can be reconstructed in this way. Requests for ad pulling and ad display can be encapsulated into objects, placed in a management container and called according to certain rules. This can not only avoid the situation where ads are being pulled when the ad display interface is called, resulting in loss of display times, but also cancel some steps when abnormal situations such as network errors occur.

Observer Pattern

definition

Sometimes called the publish/subscribe pattern, it defines a one-to-many dependency relationship, allowing multiple observer objects to listen to a subject object at the same time. When the subject object changes its state, it notifies all observer objects so that they can automatically update themselves.

Example

Java's Observable class and Observer interface implement the observer pattern. An Observer object monitors the changes of an Observable object. When the Observable object changes, the Observer is notified and can perform corresponding work.
If you are interested, you can directly check the source code of the two classes. This pattern is quite common, so I will not post it here again.

Android

The ListView adapter has a notifyDataSetChange() function, which notifies each Item in the ListView that the data source has changed and each sub-Item needs to be refreshed.

Refactoring

For example, after selecting a filter and taking a photo, the camera enters the selfie confirmation page. If the user changes the filter, the filter on the camera interface should be replaced with the latest selected filter when returning. Currently, this logic is implemented in the onResume function of the camera interface, but in fact, the filter can be extracted into a complete module, and then the observer mode can be implemented in the module. This can encapsulate the filter logic and decouple it from the outside world, which is also in line with the concept of Java multi-purpose combination.

EventBus

The benefits of EventBus are obvious. It can easily and simply implement the observer mode, but the disadvantages are also obvious.

  • Extensive abuse will lead to decentralized logic, making it difficult to locate problems.
  • There is no way to implement strong typing, and problems are found during compilation (Otto implemented this, but the performance is problematic). In terms of implementation, a very weak protocol is used, such as onEvent{XXX}, {XXX} represents ThreadModel, to achieve thread switching.
  • There are problems with code readability, and IDEs cannot recognize these protocols, which is not IDE-friendly.

Therefore, if there is a need to use the observer mode, it is recommended to implement the observer mode in this module if all conditions permit. If you find that this function is also needed in other modules, then you should consider whether this series of functions should be extracted into a more independent module for reuse, rather than using EventBus directly for the sake of convenience.

Memento Pattern

definition

Without destroying the closure, capture the internal state of an object and save the state outside the object, so that the object can be restored to the previously saved state later.

Example

Serialize objects to local storage and deserialize them back when necessary

      public PhotoData readPhotoData (String path) {
            PhotoData photoData = null ;
            ObjectInputStream objInput = null ;
            try {
                objInput = new ObjectInputStream( new FileInputStream(path));
                photoData = (PhotoData) objInput.readObject();
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            finally
                try {
                    if (objInput != null ) {
                        objInput.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return photoData;
        }

    public void writePhotoData (PhotoData data, String path) {
        ObjectOutputStream objOutput = null ;
        try {
            objOutput = new ObjectOutputStream( new FileOutputStream(path));
            objOutput.writeObject(data);
        } catch (IOException e) {
            e.printStackTrace();
        finally
            try {
                if (objOutput != null ) {
                    objOutput.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

Android

Activity's onSaveInstanceState and onRestoreInstanceState use the memo mode, which are used for saving and restoring respectively.

Refactoring

None

Iterator Pattern

definition

Provides a way to sequentially access the elements of a container object without exposing the object's internal representation.
The iterator pattern lives and dies with the collection. Generally speaking, as long as we implement a collection, we need to provide an iterator for this collection at the same time, just like Collection, List, Set, Map, etc. in Java, these collections have their own iterators. If we want to implement such a new container, of course we also need to introduce the iterator pattern and implement an iterator for our container.

Example

Java's Iterator is an abstract iterator. By implementing this interface, each collection can provide its own customized specific iterator. If you are interested, you can directly view the source code. Although we use collections in many scenarios, the actual use of iterators is relatively rare. For simple traversal (such as arrays or ordered lists), it is more cumbersome to use iterators to traverse. For example, ArrayList, we prefer to use for loops to traverse, but for collections such as hash tables, it is much simpler to introduce iterators. At the same time, we can also use custom iterators to provide forward or reverse traversal of ordered lists. Users only need to get iterators to traverse without worrying about the specific traversal algorithm.

Android

In the Android source code, the most typical example is that Cursor uses the iterator mode. When we use the query method of SQLiteDatabase, the Cursor object is returned, and then the Cursor is used to traverse the data.

Refactoring

None

Template Method Pattern

definition

Define the algorithm framework in an operation and defer some steps to subclasses so that subclasses can redefine certain specific steps of an algorithm without changing its structure.

Example

 public abstract class BaseAdvertManager {
    public void loadAdvert (Context context, int type) {
        if (NetUtils.checkNetConnection(context) != NetUtils.OK) {
            return ;
        }
        mStateArray.put(type, AdvertConstant.AD_STATE_LOADING);
        doLoadAdvert(context, type);
    }

    protected abstract void doLoadAdvert (Context context, int type) ;
}

The Ad Manager abstract class defines a general template for loading ads, but declares the specific logic of loading ads in the template as an abstract method for each subclass to implement.

Android

The process of starting an Activity is very complicated, and there are many places that need to be customized by developers. That is to say, the overall algorithm framework is the same, but some steps are delayed to the subclass, such as Activity's onCreate, onStart, etc. In this way, the subclass can redefine some specific operations without changing the overall process of starting the Activity.

Refactoring

You can try to use the template method to refactor methods that have most of the same code but different parts of the code. You can either refactor the method in the same class or refactor the subclass and parent class by shaping the template function. In the project, saving photos and sharing and saving photos and backing out on the selfie confirmation page are currently two independent methods, but most of the code inside the methods is the same, so you need to use this pattern to refactor. Since the code is large and the refactoring method is relatively simple, I will not post the code.

Visitor Pattern

definition

Encapsulates some operations that act on the elements of a data structure. It can define new operations that act on these elements without changing the data structure.
If an object contains some operations that are irrelevant (or weakly related) to the object, in order to avoid these operations polluting the object, the visitor pattern can be used to encapsulate these operations into the visitor.
If there are similar operations in a group of objects, in order to avoid a large amount of repeated code, these repeated operations can also be encapsulated in the visitor.
The purpose of the visitor pattern is to encapsulate some operations applied to the elements of a data structure. Once these operations need to be modified, the data structure that receives the operation can remain unchanged.
Visitor mode is the most complex and difficult to understand among the 23 design modes, but its usage rate is not high. In most cases, we do not need to use visitor mode, only a few specific scenarios need it.

In most cases, you need to use visitor mode, but when you need it, you really need it. ——GOF Design Pattern: The Basics of Reusable Object-Oriented Software

Example

 interface Service {

    public void accept (Visitor visitor) ;
}

class Draw implements Service {

    public void accept (Visitor visitor) {
        visitor.process( this );
    }
}

class Fund implements Service {

    public void accept (Visitor visitor) {
        visitor.process( this );
    }
}

class Saving implements Service {

    public void accept (Visitor visitor) {
        visitor.process( this );
    }
}

class Visitor {

    public void process (Service service) {
        // Basic business
        System.out.println( "Basic Business" );
    }

    public void process (Saving service) {
        // deposit
        System.out.println( "Deposit" );
    }

    public void process (Draw service) {
        // Withdrawal
        System.out.println( "withdrawal" );
    }

    public void process (Fund service) {
        System.out.println( "Fund" );
        // fund
    }
}

public class Client {
    public static void main (String[] args) {
        Service saving = new Saving();
        Service fund = new Fund();
        Service draw = new Draw();

        Visitor visitor = new Visitor();
        saving.accept(visitor);
        fund.accept(visitor);
        draw.accept(visitor);
    }
}

The benefits of using Visitor are shown above. When you need to change the processing of one of the services, you do not need to modify it in every place, but just need to change the corresponding processing functions in the Visitor class. That is to say, it is suitable for situations where business processing changes frequently.
Of course, Visitor also has its own limitations. It is not suitable for frequent changes in the number of services, because once some services are added or deleted, the Visitor needs to be added and deleted accordingly. That is to say, the specific Service and Visitor are coupled.

Android

The use of visitor mode in Android is actually mainly in the compilation annotation. The core principles of compilation annotation rely on APT (Annotation Processing Tools). Famous open source libraries such as ButterKnife, Dagger, and Retrofit are all based on APT.

Refactoring

If it is a business that often requires changes in logic, it is very suitable for using the visitor mode. If it is a business that requires frequent addition of new services, it is not suitable. Therefore, the Android UI display part is actually theoretically suitable for using the visitor mode, because the UI often changes one version at a time. If the changes in the UI are not limited to modifications in XML, but have been reflected in the code, then it can be considered whether it can be modified using the visitor mode.
This is not the case in the actual project at present. Since the subsequent UI rendering of the advertising module is based on the advertising objects sent by different advertising platforms, the parameters required for the interface of each advertising platform's rendering view are different, but you can consider doing a detachment and packaging to make it a simple Visitor to try it.

Intermediary model

definition

The mediator pattern wraps a series of ways in which objects interact, so that these objects do not have to be called clearly from each other, so that they can be easily coupled. When the effects between some objects change, the effects between some other objects will not immediately affect the effects of some other objects. The mediator pattern turns many-to-many interactions into one-to-many interactions.
In fact, the mediator object is to transform the system from a mesh structure to a star structure centered on the mediator.
To give a simple example, a computer includes: CPU, memory, graphics card, and IO devices. In fact, to start a computer, it is enough to have a CPU and memory. Of course, if you need to connect the display screen to display, you have to add a graphics card. If you need to store data, you need an IO device, but this is not the most important thing. They are just ordinary parts that are divided. We need one thing to integrate these parts into a complete body. This thing is the motherboard. The motherboard plays the role of an intermediary, and the communication between any two modules will be coordinated by the motherboard.

Example

 public abstract class Person {
    protected String name;
    protected Mediator mediator;

    Person(String name,Mediator mediator){
        this .name = name;
        this .mediator = mediator;
    }

}

public class HouseOwner extends Person {

    HouseOwner(String name, Mediator mediator) {
        super (name, mediator);
    }

    /** * @desc Contact the intermediary* @param message * @return void */
    public void constact (String message) {
        mediator.constact(message, this );
    }

    /** * @desc Get information* @param message * @return void */
    public void getMessage (String message) {
        System.out.println( "Homeowner:" + name + ",get information: " + message);
    }
}

public class Tenant extends Person {

    Tenant(String name, Mediator mediator) {
        super (name, mediator);
    }

    /** * @desc Contact the intermediary* @param message * @return void */
    public void constact (String message) {
        mediator.constact(message, this );
    }

    /** * @desc Get information* @param message * @return void */
    public void getMessage (String message) {
        System.out.println( "Renter:" + name + ",get information: " + message);
    }
}

public abstract class Mediator {
    //Declare a contact method
    public abstract void constact (String message,Person person) ;
}

public class MediatorStructure extends Mediator {
    //First of all the intermediary structure must know the information of all homeowners and renters
    private HouseOwner houseOwner;
    private Tenant tenant;

    public HouseOwner getHouseOwner () {
        return houseOwner;
    }

    public void setHouseOwner (HouseOwner houseOwner) {
        this .houseOwner = houseOwner;
    }

    public Tenant getTenant () {
        return tenant;
    }

    public void setTenant (Tenant tenant) {
        this .tenant = tenant;
    }

    public void constact (String message, Person person) {
        if (person == houseOwner){ //If it is a homeowner, the renter will get information
            tenant.getMessage(message);
        }
        else { //Anyway, it is the homeowner who gets the information
            houseOwner.getMessage(message);
        }
    }
}

public class Client {
    public static void main (String[] args) {
        //A homeowner, a renter, an intermediary agency
        MediatorStructure mediator = new MediatorStructure();

        //Homeowners and renters only need to know the agency
        HouseOwner houseOwner = new HouseOwner( "张三" , mediator);
        Tenant tenant = new Tenant( "李四" , mediator);

        //Agent structure requires knowing the homeowner and renter
        mediator.setHouseOwner(houseOwner);
        mediator.setTenant(tenant);

        tenant.constact( "I heard that you have a three-bedroom homeowner for rent..." );
        houseOwner.constact( "Yes! Do you need to rent?" );
    }
}

Homeowner: Zhang San, get information: I heard that you have a three-bedroom homeowner for rent...
Renter: Li Si, get the information: Yes! Do you need to rent?

Android

In the Binder mechanism, the intermediary mode is used. We know that when the system is started, various system services will submit registration to the ServiceManager, that is, the ServiceManager holds references to various system services. When we need to obtain the system's ServiceManager, such as ActivityManager, WindowManager, etc. (they are all Binders), first, we query the ServiceManager for the corresponding Binder of the specified identifier, and then the ServiceManager returns the Binder reference. And the communication between the client and the server is implemented through the Binder driver, where the ServiceManager and Binder drivers are intermediaries.

Refactoring

Since the beginning of the year, there has been a refactoring of MVP in the project. In the MVP architecture , the P layer is actually an intermediary, responsible for coordinating V and M.

Appearance mode/facade mode

definition

It is required that the communication between the external and internal of a subsystem must be carried out through a unified object.
For example, when we start the computer, we just need to press the turn on key, without regard to how the disk, memory, CPU, power supply, etc. are inside. We only care about them starting it up for me. In fact, because the circuit inside is too complicated, we can't understand how the internal circuit works. The host just provides the only interface to "turn on key" to the user.

Example

 /** * cpu subsystem class*/
public class CPU {
    public static final Logger LOGGER = Logger.getLogger(CPU.class);
    public void start () {
        LOGGER.info( "cpu is start..." );
    }

    public void shutDown () {
        LOGGER.info( "CPU is shutDown..." );
    }
}

/** * Disk subsystem class*/
public class Disk {
    public static final Logger LOGGER = Logger.getLogger(Disk.class);
    public void start () {
        LOGGER.info( "Disk is start..." );
    }

    public void shutDown () {
        LOGGER.info( "Disk is shutDown..." );
    }
}

/** * Memory subsystem class*/
public class Memory {
    public static final Logger LOGGER = Logger.getLogger(Memory.class);
    public void start () {
        LOGGER.info( "Memory is start..." );
    }

    public void shutDown () {
        LOGGER.info( "Memory is shutDown..." );
    }
}

/** * Facade Class (Core) */
public class Computer {
    public static final Logger LOGGER = Logger.getLogger(Computer.class);

    private CPU cpu;
    private Memory memory;
    private Disk disk;
    public Computer () {
        cpu = new CPU();
        memory = new Memory();
        disk = new Disk();
    }
    public void start () {
        LOGGER.info( "Computer start begin" );
        cpu.start();
        disk.start();
        memory.start();
        LOGGER.info( "Computer start end" );
    }

    public void shutDown () {
        LOGGER.info( "Computer shutDown begin" );
        cpu.shutDown();
        disk.shutDown();
        memory.shutDown();
        LOGGER.info( "Computer shutDown end..." );
    }
}

/** * Client class*/
public class Cilent {
    public static final Logger LOGGER = Logger.getLogger(Cilent.class);
    public static void main (String[] args) {
        Computer computer = new Computer();
        computer.start();
        LOGGER.info( "=================" );
        computer.shutDown();
    }
}

Judging from the above example, with this Facade class, that is, the Computer class, the user does not have to call the Disk, Memory, and CPU classes in the subsystem themselves. He does not need to know the implementation details within the system, nor does he even need to know the internal composition of the system. The client only needs to interact with Facade.

Android

So where does Android use the appearance mode? Still back to Context, there are many complex functions inside Android such as startActivty, sendBroadcast, bindService, etc. The internal implementation of these functions is very complex. If you read the source code, you can feel it, but we don’t need to care about what is implemented inside it. We only care about it helping us start the Activity, send a broadcast for us, bind the Activity, etc.

Refactoring

None

Proxy Mode

definition

Provides a proxy to an object, and the proxy object controls the reference to the original object.
There are several types of proxy modes: virtual proxy, counting proxy, remote proxy, and dynamic proxy. It is mainly divided into two categories: static proxy and dynamic proxy.

Static Proxy

definition

Static proxy is relatively simple. It is a proxy class written by programmers and compiled before the program runs, rather than dynamically generating proxy classes from the program. This is called static. It can be implemented through two ways: aggregation and inheritance. The inheritance method is not flexible enough, so only the aggregation method is introduced.

Example

nterface Subject {
    void request () ;
}

class RealSubject implements Subject {
    public void request () {
        System.out.println( "RealSubject" );
    }
}

class Proxy implements Subject {
    private Subject subject;

    public Proxy (Subject subject) {
        this .subject = subject;
    }
    public void request () {
        System.out.println( "begin" );
        subject.request();
        System.out.println( "end" );
    }
}

public class ProxyTest {
    public static void main (String args[]) {
        RealSubject subject = new RealSubject();
        Proxy p = new Proxy(subject);
        p.request();
    }
}

Refactoring

Currently, there are business needs in the project to load images. The frames for loading images can include ImageLoader, Glide , etc., and these third parties or their own internal tool classes can be integrated together through the adapter mode, and then provided to external use of image processing through static agents.

Dynamic Proxy

definition

In dynamic proxy, the proxy class is not implemented in Java code, but is generated during the runtime. Compared with static proxy, dynamic proxy can conveniently handle the methods of the delegate class in a unified manner, such as adding method calls, adding log functions, etc. Dynamic proxy is divided into jdk dynamic proxy and cglib dynamic proxy. Let’s use an example to see how to implement jdk dynamic proxy.

JDK dynamic proxy example

 //Define business logic
public interface Service {  
    //Target method
    public abstract void add () ;  
} 

public class UserServiceImpl implements Service {  
    public void add () {  
        System.out.println( "This is add service" );  
    }  
}

//Use the java.lang.reflect.Proxy class and java.lang.reflect.InvocationHandler interface to define the implementation of the proxy class.
class MyInvocatioHandler implements InvocationHandler {
    private Object target;

    public MyInvocatioHandler (Object target) {
        this .target = target;
    }

    @Override
    public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println( "-----before-----" );
        Object result = method.invoke(target, args);
        System.out.println( "-----end-----" );
        return result;
    }
    // Generate proxy object
    public Object getProxy () {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class[] interfaces = target.getClass().getInterfaces();
        return Proxy.newProxyInstance(loader, interfaces, this );
    }
}

//Use dynamic proxy
public class ProxyTest {
    public static void main (String[] args) {
        Service service = new UserServiceImpl();
        MyInvocatioHandler handler = new MyInvocatioHandler(service);
        Service serviceProxy = (Service)handler.getProxy();
        serviceProxy.add();
    }
}
Execution Result:

-----before-----
This is add service
-----end-----

cglib dynamic proxy example

As we analyzed earlier, because Java only allows single inheritance, and the proxy class generated by JDK itself inherits the Proxy class, the dynamic proxy implemented using JDK cannot complete the inherited dynamic proxy, but we can use cglib to implement the inherited dynamic proxy.
The famous spring contains cglib dynamic proxy, and here it also uses the cglib built in Spring to complete the implementation of dynamic proxy:

 //1. Specific topic
public class Train {  
    public void move () {  
        System. out .println( "The train is moving..." );  
    }  
}  
//2. Generate a proxy
public class CGLibProxy implements MethodInterceptor {  
    private Enhancer enhancer = new Enhancer();  
    public Object getProxy ( Class clazz ) {  
        enhancer.setSuperclass(clazz);  
        enhancer.setCallback( this );  
        return enhancer.create();  
    }  
    /** * Intercept all target class methods calls* Parameters: * obj target instance object* method reflect object* args method parameters* proxy proxy class instance*/  
    public Object intercept ( Object obj, Method method, Object[] args, MethodProxy proxy ) throws Throwable {  
        //The proxy class calls the parent class method
        System. out .println( "Login Start" );  
        proxy.invokeSuper(obj, args);  
        System. out .println( "Login End" );  
        return null ;  
    }  
}  
//3. Test
public class Test {  
    public static void main ( String[] args ) {  
        CGLibProxy proxy = new CGLibProxy();  
        Train t = (Train) proxy.getProxy(Train.class);  
        t.move();  
    }  
}

Refactoring

In many places in the project, statistics are needed. It feels inappropriate to put this function on any layer. Simple statistics can be extracted through dynamic proxy. Dynamic proxy is actually a concrete implementation of AOP programming idea

summary

Compared with static proxy, the biggest advantage is that all methods declared in the interface are transferred to a centralized method to call the processor for processing. When the number of interface methods is large, we can handle it flexibly without the need to process each method or method combination like a static proxy. Proxy is beautiful and powerful, but only supports interface proxy. Java's single inheritance mechanism destined that these dynamic proxy classes cannot implement dynamic proxy for class. Fortunately, cglib provides compensation for Proxy. The difference between class and interface is vague, and some new features have been added in java8, making interface closer and closer to class. When one day, Java breaks through the limitation of single inheritance, dynamic proxy will be more powerful.

Android

AIDL will judge whether to access cross-process based on the current thread. If you do not need to cross-process, you will return the instance directly, and if you need to cross-process, you will return a proxy. When cross-process communication, you need to write parameters to the Parcelable object, and then execute the transact function. AIDL generates a proxy class, which will automatically help us write these operations.
To realize plug-in development of Android, dynamic proxy is even more indispensable.

The difference between intermediary, agent, and appearance mode

  • Mediator pattern: The dialogue between A and B is conveyed through C. A and B can not recognize each other (reduces coupling between A and B objects)
  • Agent mode: A wants to give B a gift, A and B don’t know each other, so A can find C to help it realize its wish to give gifts (encapsulate object A)
  • Appearance mode: A and B need to implement the method of sending flowers and chocolates, so I can implement the method of sending flowers and chocolates through an abstract class C (A and B both inherit C). (Embroidered subclasses A and B)

The main difference between the two modes: proxy mode and appearance mode is that proxy mode targets a single object, while appearance mode targets all subclasses.

Decorative mode

definition

Dynamically adds extra responsibilities to an object, and in terms of adding functions, the decorative pattern is more flexible than the method of subclass inheritance.

We can usually use inheritance to achieve function expansion. If there are many types of functions that need to be expanded, then many subclasses will inevitably be generated to increase the complexity of the system. At the same time, use inheritance to achieve function expansion, we must foresee these expansion functions. These functions are determined at compile time and are static.

The reason for using Decorator is that these functions need to be dynamically determined by the user to join. Decorator provides a "plug-and-play" method to decide when to add what functions during operation.

Example

 public abstract class Component {
    public abstract void operate () ;
}

public class ConcreteComponent extends Component {
    public void operate () {
        //Specific implementation
    }
}

public class Decorator extends Component {
    private Component component;
    public Decorator (Component component) {
        this .component = component;
    }

    public void operate () {
        operateA();
        component.operate();
        operateB();
    }
    public void operateA () {
        //Specific operation
    }
    public void operateB () {
        //Specific operation
    }
}

public static void main (String[] args) { 
        // Use ordinary function classes
        Component concreteComponent = new ConcreteComponent();
        Component decorator = new Decorator(concreteComponent);
        decorator.operate();
    } 
}

If you are careful, you will find that the above call is similar to the call we read when we read the file:

FileReader fr = new FileReader(filename);
BufferedReader br = new BufferedReader(fr);

In fact, Java's I/O API is implemented using Decorator. There are many I/O variants. If all inheritance methods are adopted, many subclasses will be produced, which is obviously quite cumbersome.

Android

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

Refactoring

If there are too many subclasses, you can consider using it

The difference between decorative mode and proxy mode

Decorative mode: extending the function of an object in a transparent way to the client is an alternative to inheritance relationships;
Proxy mode: Provide an object with a proxy object, and there is a proxy object to control references to the original object;
The decorative mode should enhance the functions of the objects being decorated; the proxy mode exerts control over the objects of the agent and does not provide enhanced functions of the object itself. If you write decorations in one place, you will know that this is adding functions. If you write an agent, you will know that it is limiting.

Combination mode

definition

The objects are formed into a tree structure to represent a "part-total" hierarchy, allowing users to have consistency in their use of individual objects and combined objects.

Example

 public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List subordinates;

   //Constructor
   public Employee ( String name,String dept, int sal ) {
      this .name = name;
      this .dept = dept;
      this .salary = sal;
      subordinates = new ArrayList();
   }

   public void add ( Employee e ) {
      subordinates.add(e);
   }

   public void remove ( Employee e ) {
      subordinates.remove(e);
   }

   public List getSubordinates () {
     return subordinates;
   }

   public String toString () {
      return ( "Employee :[ Name : " + name
      + ", dept : " + dept + ", salary :"
      + salary+ " ]" );
   }   
}

public class CompositePatternDemo {
   public static void main ( String[] args ) {
      Employee CEO = new Employee( "John" , "CEO" , 30000 );

      Employee headSales = new Employee( "Robert" , "Head Sales" , 20000 );

      Employee headMarketing = new Employee( "Michel" , "Head Marketing" , 20000 );

      Employee clerk1 = new Employee( "Laura" , "Marketing" , 10000 );
      Employee clerk2 = new Employee( "Bob" , "Marketing" , 10000 );

      Employee salesExecutive1 = new Employee( "Richard" , "Sales" , 10000 );
      Employee salesExecutive2 = new Employee( "Rob" , "Sales" , 10000 );

      CEO.add(headSales);
      CEO.add(headMarketing);

      headSales.add(salesExecutive1);
      headSales.add(salesExecutive2);

      headMarketing.add(clerk1);
      headMarketing.add(clerk2);

      //Print all employees of the organization
      System. out .println(CEO);
      for (Employee headEmployee : CEO.getSubordinates()) {
         System. out .println(headEmployee);
         for (Employee employee : headEmployee.getSubordinates()) {
            System. out .println(employee);
         }
      }       
   }
}

Android

The structure of a View in Android is a tree structure. Each ViewGroup contains a series of Views, and the ViewGroup itself is a View. This is a very typical combination pattern in Android.

Refactoring

None

Adapter mode

definition

Transform the interface of one class into another that the client expects, so that two classes that were originally unable to work together due to interface mismatch can work together.

Example

 // Target interface, or standard interface, is the interface that customers expect
interface Target { 
    public void request () ; 
} 

// Specific target class, only ordinary functions are provided
class ConcreteTarget implements Target { 
    public void request () { 
        System.out.println( "Ordinary class has normal functions..." ); 
    } 
} 

// Classes that already exist, have special functions, but do not meet our existing standard interfaces, are also classes that need to be adapted
class Adaptee { 
    public void specificRequest () { 
        System.out.println( "The adapted class has special functions..." ); 
    } 
}   

// Adapter class, inherits the adapted class, and implements standard interfaces
class Adapter extends Adaptee implements Target { 
    public void request () { 
        super .specificRequest(); 
    } 
} 

// Test class public class Client {
public static void main (String[] args) { 
        // Use ordinary function classes
        Target concreteTarget = new ConcreteTarget(); 
        concreteTarget.request(); 

        // Use special function classes, i.e. adapter classes
        Target adapter = new Adapter(); 
        adapter.request(); 
    } 
}

Android

Typical are ListView and RecyclerView . Why do ListViews need to use adapters? ListView is used to display list data, but list data has many forms. In order to process and display different data, we need the corresponding adapter as a bridge. In this way, ListView can only care about each ItemView, without caring about what the ItemView is specifically displayed. Our data source stores the content to be displayed, which saves the content to be displayed in each ItemView. There is no relationship between ListView and the data source. At this time, the adapter needs to provide the getView method for the ListView to use. Each time ListView only needs to provide location information to the getView function, and then the getView function obtains the corresponding data from the data source according to the location information, and returns different Views according to the data.

Refactoring

When you want to use an existing class interface, but this existing class is incompatible with the current code structure, you can consider using the adapter mode.

Enjoy the Yuan mode

definition

The English word for Xiangyuan mode is Flyweight, which refers to the lightest weight level in boxing competitions, that is, "flyweight" or "rain level". The translation of "Xianyuan mode" is chosen here because it can better reflect the intention of the mode. Xiangyuan mode is the structure mode of the object. Xiangyuan mode efficiently supports a large number of fine-grained objects in a shared way.

Enjoy the Yuan mode is also divided into simple Enjoy the Yuan mode and composite Enjoy the Yuan mode

Example

In the JAVA language, the String type uses the meta-saving pattern. The String object is a final type, and the object cannot be changed once it is created. In JAVA, string constants are all in the constant pool. JAVA will ensure that a string constant has only one copy in the constant pool. String a=”abc”, where “abc” is a string constant.

Android

We usually have a lot of contact with the Xiangyuan mode, such as constant pools, thread pools, etc. in Java. It is mainly for reusing objects.

Where do you use the Xiangyuan mode on Android? Message in thread communication, calling Message.obtain() every time we get Message is actually to retrieve reusable messages from the message pool to avoid generating a large number of Message objects.

Refactoring

None

Bridge Mode

definition

Separate the abstract parts from the implementation parts so that they can change independently.
In fact, a class has two dimensions of changes, and both dimensions need to be expanded.

Take cars driving on the road as an example. There are both cars and buses. They can not only drive on highways in the city, but also on highways. You will find that there are different types of vehicles (cars), and the environment (road) they drive also have different types. In the software system, we must adapt to changes in two aspects (different models and different roads). How can we achieve this change?

In software systems, some types have two or more dimensions due to their own logic. How to deal with this "multi-dimensional change"? How to use object-oriented technology to make the type easily change in multiple directions without introducing additional complexity? This requires the use of Bridge mode. Bridge mode is a very useful pattern and very complex. It conforms to the open-closed principle and preferential use of objects rather than inheriting these two object-oriented principles.

Example

First, let’s take a look at the above example code of the car when the bridge mode is not applied:

 //Basic path
class Road { 
    void run () { 
        System.out.println( "road" ); 
    } 
} 
//Downtown street
class Street extends Road { 
    void run () { 
        System.out.println( "Downtown Street" ); 
    } 
} 
//highway
class SpeedWay extends Road { 
    void run () { 
        System.out.println( "Highway" ); 
    } 
} 
//The car is driving in the city streets
class CarOnStreet extends Street { 
    void run () { 
        System.out.println( "The car is driving in the city street" ); 
    } 
} 
//The car is driving on the highway
class CarOnSpeedWay extends SpeedWay { 
    void run () { 
        System.out.println( "The car is driving on the highway" ); 
    } 
} 
//The bus is driving in the city streets
class BusOnStreet extends Street { 
    void run () { 
        System.out.println( "Buses travel in downtown streets" ); 
    } 
} 
//The bus is driving on the highway
class BusOnSpeedWay extends SpeedWay { 
    void run () { 
        System.out.println( "Buses travel on highways" ); 
    } 
} 
//test
public static void main (String[] args) { 

    //The car is driving on the highway
    CarOnSpeedWay carOnSpeedWay = new CarOnSpeedWay(); 
    carOnSpeedWay.run(); 
    //The bus is driving in the city streets
    BusOnStreet busOnStreet = new BusOnStreet(); 
    busOnStreet.run(); 

}

However, we say that such a design is fragile. If you analyze it carefully, you will find that it still has many problems. First, while following the principle of open-closing, it violates the single responsibility principle of the class, that is, there is only one reason for its change in a class, and there are two reasons for the change here, namely, the change in the road type and the change in the car type; secondly, there will be many repeated codes, and different cars will also have some codes that are the same when driving on different roads;

Again, the structure of the class is too complex, there are too many inheritance relationships, and it is difficult to maintain. The last and most fatal point is that the scalability is too poor. If the change changes along the type of the car and the different roads, we will see that the structure of this class will quickly become huge.
Next, let's take a look at the code after applying the bridge mode:

 abstract class AbstractRoad { 
    AbstractCar aCar; 
    void run () {}; 
} 
abstract class AbstractCar { 
    void run () {}; 
} 

class Street extends AbstractRoad { 
    @Override 
    void run () { 
        // TODO Auto-generated method stub
        super .run(); 
        aCar.run(); 
        System.out.println( "Driving in downtown streets" ); 
    } 
} 
class SpeedWay extends AbstractRoad { 
    @Override 
    void run () { 
        // TODO Auto-generated method stub
        super .run(); 
        aCar.run(); 
        System.out.println( "Driving on the highway" ); 
    } 
} 
class Car extends AbstractCar { 
    @Override 
    void run () { 
        // TODO Auto-generated method stub
        super .run(); 
        System.out.print( "Car" ); 
    } 
} 
class Bus extends AbstractCar { 
    @Override 
    void run () { 
        // TODO Auto-generated method stub
        super .run(); 
        System.out.print( "Bus" ); 
    } 
} 

public static void main (String[] args) { 

    AbstractRoad speedWay = new SpeedWay(); 
    speedWay.aCar = new Car(); 
    speedWay.run(); 

    AbstractRoad street = new Street(); 
    street.aCar = new Bus(); 
    street.run(); 
}

It can be seen that through object combination, the Bridge pattern changes the inheritance relationship between the two characters into a coupled relationship, so that the two can change independently and calmly, which is also the original intention of the Bridge pattern.
This increases the coupling between the client program and the road and the car. In fact, this worry is unnecessary, because this coupling is caused by the creation of objects, and can be solved by creating patterns. In addition, the creation design patterns are used to deal with specific problems.

Android

There are many bridge modes used in Android. For example, for a View, it has two dimensions of changes. One is its description, such as Button, TextView, etc., which are changes in the dimension of the View. The other dimension is to actually draw the View on the screen, which is related to Display, HardwareLayer and Canvas. These two dimensions can be regarded as applications of bridge mode.

Refactoring

None

Insights

This article was written based on multiple blog posts, actual projects and personal understanding before starting to read the Gang of Four's "Design Pattern: The Basics of Reusable Object-Oriented Software".

It is recommended to look at these two ideas when you have accumulated some accumulation or even encounter some problems that you no longer need to solve with your past experience (this solution refers to the best solution, not just to solve it). Resonance is really important.

As for learning the complex technical knowledge of this system, I never recommend reading only a few blog posts. This will only be left on the surface. In addition to refusing to talk about it on paper, many of the most important contents can often be explained in detail in the book, such as how to coordinate various models, what are the differences between each model, and a variety of usage scenarios. How can a few blog posts be learned when one model can write a paper?

The first time I watched the design model was two and a half years ago. I hadn't graduated yet. I watched "Da Talk Design Model". At that time, I didn't have much accumulation. The most important thing about the architecture was MVC. So even if I watched "Da Talk", which is famous for its popular "Da Talk", it was still in a fog, and I gave up after reading it for only two chapters.

However, after the continuous accumulation of code volume and continuous discovery of problems, I read "Refactoring" and "Design Pattern" again. At this time, I felt completely different. Especially after I did the project reconstruction for a while ago, it is no exaggeration to say that when I read the book "Refactoring", every chapter had the feeling that I felt that I had met too late.

The growth in technology is multifaceted. On the one hand, it depends on the accumulation of experience, but the accumulation of experience is limited by many practical factors. On the other hand, the growth is ideological evolution. Of course, this can also be improved by actually doing projects. But similarly, learning through projects will one day reach a bottleneck, which means you should reap the thoughts left by the giants.

I have seen theories such as reconstruction and design patterns in some places before. In fact, it is not that reconstruction and design patterns are useless, but that I have not yet reached the level of thinking that they are useful. Design patterns are by no means good topics to talk about definitions occasionally. As one of the three must-read books for Java programmers, "Design Patterns", which can be passed down for such a long time, has its roots.

Finally, I would like to complain that the book "Design Pattern" is really a magical book, but it is really obscure and difficult to understand. A large part of the reason is that the four masters of the Gang of Four have a deep understanding of the design pattern, which leads to extremely concise and abstract expressions when writing the book. So after sticking to the first two chapters, he once again gave up and turned to the embrace of "Head First: Design Pattern". The former has more than 200 pages and the latter has more than 700 pages, but everyone knows which one is better to read.

However, reconstruction and design patterns themselves cannot be achieved overnight. Both of them need to be continuously applied, re-understood, and re-applied. Only after repeating them can you fully integrate them. Therefore, there is no need to worry about mastering them all after reading them today. Look at them every once in a while, and look back at them when encountering problems. This is the right way to learn these two major ideas.

Putting knowledge like "Refactoring" and "Design Patterns" that is more thought-oriented at the beginning of the year is also to further study "Effective Java", "Java Programming Thoughts" and Android source code in the future. The best way to read the source code is not to start from the first line, or to start from the problem, with points and surfaces; or you first try to start from the coder's ideological level. Sometimes when you discover the design ideas and patterns of this module, many things are understood at one point. You even know that you will know the general content of the entire module when you see the class structure of a module, and then just like the code you wrote yourself, it is easy to find anything. Just like if you don't understand generics, even if you have written four or five major projects, you will be confused when you see a bunch of source codes using generic third-party libraries.

refer to

  • Memorize 23 design patterns from Android code
  • Factory method mode in Android source code
  • Android Design Pattern (Twelve) - Abstract Factory Pattern
  • Java design pattern 2: Abstract Factory / Factory Method
  • Command mode in Android source code
  • Java Design Pattern Series Iterator Pattern
  • Visitor Mode of "JAVA and Mode"
  • Design Pattern Reading Notes – Intermediary Pattern
  • Java design mode outside appearance mode (facade mode)
  • Combination mode
  • A preliminary study on JAVA design mode: Adapter mode
  • Java Design Pattern (7) Decorator Pattern (Decorator Pattern)
  • "JAVA and Mode" Enjoy the Yuan Mode
  • A preliminary study on the bridge mode of JAVA design mode
  • Talk about Java proxy mode
  • The difference between Java proxy mode and decorator mode
  • Java Design Pattern—Agent Mode Implementation and Principles

<<:  Tutorial on using TensorFlow on iOS (Part 1)

>>:  0% pride! The WP system also has something worth learning from

Recommend

Apple releases OS X Yosemite beta 5, continues to optimize Photos app

[[130368]] Apple today released the fifth beta of...

Content operation, do you know the strategies of these four important links?

This is an era of information overload. Even very...

Why was the Melatonin advertisement successful?

From a user research perspective, it is more than...

Netizens test: Can cats always land on all fours? Go home and try it!

There was a very popular test video on the Intern...

Resembling "Hot Wheels", the Nomin River volcano group "appeared on camera"!

The Nuominhe volcanic group in the Oroqen Autonom...

Animals imitate humans in fighting! Has your dog ever imitated you?

Review expert: Li Weiyang, a well-known popular s...

Technology Morning News | Shanghai adds 3 medium-risk areas

【Today’s cover】 Golden rays of light pierce throu...

Salt, the first mineral discovered and eaten by humans

Salt is a necessity for human survival. Throughou...

Can you see the vortex of Coriolis force in the toilet?

Produced by: Science Popularization China Produce...

The key points of KFC’s “Crazy Thursday” marketing!

A day or two ago, KFC successfully registered &qu...