One, foreword

The creation pattern abstracts the instantiation process of classes and can separate the creation and use of objects in software modules. In order to make the software structure more clear, the outside world only needs to know their common interface for these objects, but does not know their specific implementation details, so that the design of the whole system is more in line with the principle of single responsibility.

In short, compared to the other two types (structural and behavioral), the creation pattern focuses on how objects are created

This article briefly introduces several common creation patterns:

  1. The singleton pattern
  2. The factory pattern
  3. Builder model

Note that the prototype pattern is not included in this article, so you can surf and search for more

Create mode: singleton mode

1. Schema definition

The purpose of the singleton pattern is to ensure that a class has only one instance and to provide a global access point to it. The singleton class has a private constructor that ensures that users cannot instantiate it directly with the new keyword. In addition, the pattern contains a static private member variable and a static public factory method. The factory method is responsible for verifying the existence of the instance and instantiating itself, then storing it in static member variables to ensure that only one instance is created.

Photo credit: Drawn by myself

2. Method of use

Singletons are simple to use and easy to understand. Here, I will come straight to the point and introduce four common ways to use them

2.1 the hungry

public class Singleton1 {

    private static final Singleton1 instance = new Singleton1();

    public Singleton1(a) {}public static Singleton1 getInstance(a) {
        return instance;
    }

    public void doSomething(a) {
    		//doSomething}}Copy the code

The hungry type is one of the most simple in common use method, the JVM class loading mechanism to guarantee the class loading process is thread-safe, so using multithreading environment is also no problem

The only problem with the hunchman singleton pattern may be that it is not lazy to load, and instance instances are created as soon as the singleton class is loaded by the virtual machine. At this point, if it is not used in the pre-initialization example project, it is a waste of initialization time and memory space

However, given the usage of the singleton pattern, I believe that in most cases hunger-handedness is not an unnecessary burden on the project.

2.2 Lazy: Static inner class

public class Singleton2 {

    private Singleton2(a) {}public static Singleton2 getInstance(a) {
        return SingletonHolder.instance;
    }

    private static class SingletonHolder {
        private static final Singleton2 instance = new Singleton2();
    }

    public void doSomething(a) {
    		//doSomething}}Copy the code

As with the code examples in 2.1, static inner classes are also secured by the JVM classloading mechanism in a multithreaded environment. At the same time, because it is not declared in local variables, there is no need to worry about creating objects that are temporarily not needed, even if the singleton class is loaded by the VIRTUAL machine due to non-subjective factors.

The singleton mode of static inner class can not only ensure the safety of multi-threaded environment but also realize lazy loading, and the code is more convenient to write, which is also a way that the author personally prefers to use

2.3 Lazy: double check lock DCL

public class Singleton3 {

    private volatile static Singleton3 instance;

    private Singleton3(a) {}public static Singleton3 getInstance(a) {
        if (instance == null) {
            synchronized (Singleton3.class) {
                if (instance == null) {
                    instance = newSingleton3(); }}}return instance;
    }

    public void doSomething(a) {
    		//doSomething}}Copy the code

DCL(DoubleCheckLock) is probably the most widely spread singleton pattern, and it is also the singleton pattern that the author came into contact with most in the early days of his work. The DCL’s approach to double-checking locks takes into account both thread safety and lazy loading, and addresses two of the most important keywords in Java concurrent programming: Compared with the above two examples, VOLATILE and synchronized, DCL can be pointed out more points. The author suspects that this is the reason why DCL spreads so widely

In the actual project development, DCL is also a recommended way to use if the steps are not cumbersome and the amount of code is acceptable

In JDK8, new and initialization are guaranteed to be atomic, and JIT reorders will not occur

2.4 the enumeration class

public enum Singleton4 {
    
    getInstance();

    public void doSomething(a) {
    		//doSomething}}Copy the code

After Java1.5, the singleton implementation has finally received a new face: enumerated class singleton, which is also recommended by Effective Java author Joshua

Enumerated classes have no constructors, and the creation of enumerated classes is completely internal to the JVM and not open to the outside world. This feature also prevents us from using new to create enumerated objects, and the corresponding disadvantage is that the singleton pattern of enumerated classes is not lazily loaded

In addition to being thread-safe, enumerated class singletons add a new feature: preventing singletons from being broken for two reasons:

  1. Enumeration classes cannot be created by reflection

    Forced to use reflection to create will receive error: Java. Lang. IllegalArgumentException: always reflectively create enum objects

  2. Enumeration classes cannot be deserialized

    In serialization, only the name attribute of the enumeration object is printed to the result. In deserialization, the enumeration object is found by name using the valueOf method of java.lang.Enum.

    At the same time, the compiler does not allow any customization to this serialization mechanism, so the writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods are disabled.

Enumeration classes cannot inherit from any other class. Enumeration classes inherit from java.lang.enum when compiled. Enumeration classes cannot inherit from any other class due to the single-inheritance nature of Java, but enumeration classes can implement interfaces

3. Destroy singleton mode and prevent singleton from being destroyed

As mentioned in Section 2.4, with the exception of enumeration class singletons, the other three singletons can be used to break uniqueness in the process by using reflection

Singleton classes cannot be created by reflection and deserialization due to their particularity, so the uniqueness of objects cannot be broken for the time being, but the values of enumerated classes can be modified

I’ve written a simple example code here to break singletons, so you can try it out for yourself in unit tests


    @Test
    public void main(a) {
        testSingleton1();
        testSingleton2();
        testSingleton3();
    }

    /*1. */
    private static void testSingleton1(a) {
        Object fromInstance = Singleton1.getInstance();
        Object fromReflect = createClassWithReflect(Singleton1.class);
        System.out.println(fromInstance.equals(fromReflect));
    }

    /*2. Lazy mode - static inner class */
    private static void testSingleton2(a) {
        Object fromInstance = Singleton2.getInstance();
        Object fromReflect = createClassWithReflect(Singleton2.class);
        System.out.println(fromInstance.equals(fromReflect));
    }

    /*3. DoubleCheckLock*/
    private static void testSingleton3(a) {
        Object fromInstance = Singleton3.getInstance();
        Object fromReflect = createClassWithReflect(Singleton3.class);
        System.out.println(fromInstance.equals(fromReflect));
    }

    private static <T> T createClassWithReflect(Class<T> clz) { Constructor<? > constructor = clz.getDeclaredConstructor(); constructor.setAccessible(true);
        return (T) constructor.newInstance();
    }

Copy the code

Print the result

false
false
false
Copy the code

As can be seen from the printed results, all three implementations except enumerated classes are corrupted and can create different instance objects through reflection to break the uniqueness of the singleton pattern

Reflection to create enumeration class will be affected by an error message: Java. Lang. IllegalArgumentException: always reflectively create enum objects, the author did not show here, interested students can have a try on their own

Last but not least, other articles on the web have mentioned using the Clone method to break singletons. I don’t think this is true, because users cannot call the Clone method to create new instances without overwriting the Clone () method and changing the access modifier to public

4, summary

At this point, several common singleton mode implementation methods are introduced, simple summary: the above several implementation methods can ensure the security of multi-threaded environment; In addition to enumerating singletons, singletons can be broken in several other ways

The code covered in this section is here

Create pattern: factory method

1. Schema definition

Also known as the factory pattern, factory method pattern in the factory method pattern, factory parent class is responsible for the public interface of the object definition to create products, while factory subclass object is responsible for generating the specific products, the purpose is to delay product class instantiation operations to complete, factory subclasses by factory subclasses to determine exactly which a specific product class to instantiate.

The factory method pattern contains four roles:

  1. Abstract productIs the interface that defines the product, is the supertype of the object created by the factory method pattern, that is, the common parent or interface of the product object;
  2. Specific productsAbstract product interfaces are implemented, and concrete products of a certain type are created by specific concrete factories, often corresponding to each other.
  3. The abstract factoryIt is the core of the factory method pattern. Any factory class that creates objects in the pattern must implement this interface.
  4. The specific factoryIs a subclass of the Abstract factory class that implements factory methods defined in an abstract factory and can be called by a customer to return an instance of a concrete product class.

Photo credit: Drawn by myself

2. Code examples

Role 1: Abstract product class

public abstract class AbstractProduct {

    public abstract String getName(a);

}
Copy the code

Role 2: Product implementation class

public class ProductA extends AbstractProduct {

    @Override
    public String getName(a) {
        return "A"; }}public class ProductB extends AbstractProduct {

    @Override
    public String getName(a) {
        return "B"; }}Copy the code

Role 3: Abstract factory class

public abstract class AbstractFactory {

    public abstract AbstractProduct createProduct(a);

}
Copy the code

Role 4: Factory implementation class

public class ProductAFactory extends AbstractFactory {

    @Override
    public AbstractProduct createProduct(a) {
        return newProductA(); }}public class ProductBFactory extends AbstractFactory {

    @Override
    public AbstractProduct createProduct(a) {
        return newProductB(); }}Copy the code

Example factory method use

@Test
public void main(a) {
    AbstractFactory factoryA = new ProductAFactory();
    AbstractFactory factoryB = new ProductBFactory();

    AbstractProduct productA = factoryA.createProduct();
    AbstractProduct productB = factoryB.createProduct();

    System.out.println("Name of product produced by Factory A:" + productA.getName());
    System.out.println("Name of product produced by Factory B:" + productB.getName());

}
Copy the code

Print the result

Product name of factory A: Product name of factory B: BCopy the code

3, source anchor point

From the beginning of this section, the author will illustrate each design pattern with Java/Android source code examples, using the anchor memory method to assist memory; For example, mentioning Builder mode is like mentioning Android AlertDialog, and mentioning Observer mode is like mentioning OnClickListener

The factory pattern is embodied in the source code, and we can remember the Iterator of the Java container class: Iterator

To refresh our memory, let’s briefly break down the roles of iterators using the factory method:

  1. The abstract factory
    1. 可迭代
  2. Factory implementation
    1. ArrayList
    2. HashMap
  3. Abstract product
    1. Iterator
  4. Product realization(jdk1.8)
    1. Itr class in ArrayList
    2. In the HashMap EntryIterator KeyIterator/ValueIterator class

From the above structure, the Iterator methods in ArrayList and HashMap are essentially factory methods for new objects, where the iterator method constructs and returns a concrete iterator

4, summary

The main advantage of factory method mode is that it does not need to modify the existing system when adding new product classes, and encapsulates the creation details of product objects, so the system has good flexibility and scalability. Its disadvantage is that new factories need to be added when new products are added, which leads to a pairwise increase in the number of system classes and increases the complexity of the system to a certain extent

I don’t mention simple factories here. In fact, when you remove the abstract factory class from the factory method, you get the simple factory pattern, so you can think of simple factories as simplified versions of factory methods. The author does not give examples here. If you want to know more, you can see the answer of Alibaba Tao System technology on Zhihu: what is the difference between simple factory mode, factory method mode and abstract factory mode

The code covered in this section is here

Create pattern: Abstract factory

1. Schema definition

Abstract factory pattern is the most abstract and general form of all factory patterns. The main difference between the abstract factory pattern and the factory method pattern is that the factory method pattern targets one product level structure, while the abstract factory pattern needs to face multiple product level structures

The Abstract Factory pattern also contains four roles:

  1. The abstract factoryMethods used to declare the generation of abstract products;
  2. The specific factoryAbstract factory declaration is implemented to generate abstract products, a set of concrete products, these products constitute a product family, each product is located in a product hierarchy;
  3. Abstract productDeclare interfaces for each product and define abstract business methods for the product in the abstract product;
  4. Specific productsDefine concrete product objects produced by concrete factories to implement business methods defined in abstract product interfaces.

Although the abstract factory has the same number of roles as the factory method, both four, it is slightly more difficult to understand because there are other roles under each role, which can also be distinguished from the UML class diagrams of the abstract factory and the factory method. Abstract factory is significantly more complex

Abstract factory is a bit complicated, so it is no longer possible to use ProductA, FactoryB and other meaningless class names as examples. To facilitate understanding, the author uses mobile phone assembly factory as an analogy, and their roles are divided as follows:

  1. Abstract product
    1. Battery assembly plant
    2. Screen assembly plant
  2. Specific product realization
    1. The battery
      1. Byd Battery Supplier
      2. Desai battery supplier
    2. The screen
      1. Boe screen supplier
      2. LG Screen Supplier
  3. The abstract factory
    1. Mobile phone assembly plant
  4. Specific factory implementation
    1. Huawei P50 assembly plant
    2. An assembly plant for the Mi 10

Given the structure of the roles above, it may be easier to understand the way in which the following code examples are written

Photo credit: Drawn by myself

2. Code examples

Role 1-1: Abstract Product class – Batteries

public abstract class AbstractProductBattery {

    public AbstractProductBattery(a) {
        createBattery();
    }

    public abstract String getBatteryName(a);

    protected abstract void createBattery(a);

}
Copy the code

Role 1-2: Abstract product class – Screens

public abstract class AbstractProductScreen {

    public AbstractProductScreen(a) {
        createScreen();
    }

    public abstract String getScreenName(a);

    protected abstract void createScreen(a);

}
Copy the code

Role 2-1-1: Product Realization Class – Batteries – BYD

public class BYDBattery extends AbstractProductBattery {

    @Override
    public String getBatteryName(a) {
        return "BYD";
    }

    @Override
    protected void createBattery(a) {
        System.out.println("Working overtime to produce BYD batteries..."); }}Copy the code

Role 2-1-2: Product Realization – Battery – Desai

public class DesayBattery extends AbstractProductBattery {

    @Override
    public String getBatteryName(a) {
        return "Desai (Desay)";
    }

    @Override
    protected void createBattery(a) {
        System.out.println("Working overtime to produce desai batteries..."); }}Copy the code

Role 2-2-1: Product Implementation Class – Screen – BOE

public class BOEScreen extends AbstractProductScreen {

    @Override
    public String getScreenName(a) {
        return "BOE";
    }

    @Override
    protected void createScreen(a) {
        System.out.println("Working overtime to produce boe screens..."); }}Copy the code

Role 2-2-2: Product Implementation Class – Screen -LG

public class LGScreen extends AbstractProductScreen {

    @Override
    public String getScreenName(a) {
        return "LG";
    }

    @Override
    protected void createScreen(a) {
        System.out.println("Working overtime to produce LG screens..."); }}Copy the code

Role 3: Abstract factory class

public abstract class AbstractPhoneFactory {

    protected String brand;// The mobile phone brand produced by the factory
    protected String model;// Factory-made mobile phone model
    protected AbstractProductScreen phoneScreen;// The screen used by the phone
    protected AbstractProductBattery phoneBattery;// The battery used

    public AbstractPhoneFactory(String brand, String model) {
        this.brand = brand;
        this.model = model;
        this.phoneScreen = createPhoneScreen();
        this.phoneBattery = createPhoneBattery();
    }

    public String getBrand(a) {
        return brand;
    }

    public String getModel(a) {
        return model;
    }

    public AbstractProductScreen getPhoneScreen(a) {
        return phoneScreen;
    }

    public AbstractProductBattery getPhoneBattery(a) {
        return phoneBattery;
    }

    protected abstract AbstractProductScreen createPhoneScreen(a);

    protected abstract AbstractProductBattery createPhoneBattery(a);
}
Copy the code

Role 4-1: Factory Implementation – Huawei

public class HuaWeiPhoneFactory extends AbstractPhoneFactory {

    public HuaWeiPhoneFactory(a) {
        super("HuaWei (HuaWei)"."P50");
    }

    @Override
    protected AbstractProductScreen createPhoneScreen(a) {
        return new LGScreen();/ / Lg screen
    }

    @Override
    protected AbstractProductBattery createPhoneBattery(a) {
        return new DesayBattery();// Desai's battery}}Copy the code

Role 4-2: Factory implementation class – Xiaomi

public class XiaoMiPhoneFactory extends AbstractPhoneFactory {

    public XiaoMiPhoneFactory(a) {
        super("Millet (XiaoMi)"."10");
    }

    @Override
    protected AbstractProductScreen createPhoneScreen(a) {
        return new BOEScreen();// Jingdong fang screen
    }

    @Override
    protected AbstractProductBattery createPhoneBattery(a) {
        return new DesayBattery();// Desai battery}}Copy the code

Example of abstract factory use

    public void main(a) {
        AbstractPhoneFactory phoneFactory1 = new HuaWeiPhoneFactory();
        AbstractPhoneFactory phoneFactory2 = new XiaoMiPhoneFactory();
        print(phoneFactory1);
        print(phoneFactory2);
    }

    private void print(AbstractPhoneFactory phoneFactory) {
        System.out.println("Production line brand:" + phoneFactory.getBrand()
                + ", production model:" + phoneFactory.getModel()
                + ", battery manufacturer: + phoneFactory.getPhoneBattery().getBatteryName()
                + ", screen manufacturer:" + phoneFactory.getPhoneScreen().getScreenName());
    }
Copy the code

Print the result

Production line brand: HuaWei, production model: P50, battery manufacturer: Desay, screen manufacturer: LG, production line brand: XiaoMi, production model:10, Battery manufacturer: Desay screen manufacturer: BOECopy the code

As can be seen from the printing results, both Xiaomi and Huawei use Desai’s batteries, but the screen suppliers are not the same. If we want to add a product line, we just inherit/implement AbstractPhoneFactory class just like Xiaomi and Huawei factories. We can choose the supplier from which each part comes. In the same way, we can add suppliers to enrich our products, which is the benefit of the abstract factory model

3, source anchor point

Abstract factory method mode in Android source code implementation is relatively less, in the “Android source code design pattern analysis and combat” book mentioned is the Android bottom of the creation of MediaPlayer can be seen as an abstract factory, this piece of code I am not familiar with, in order to prevent mistakes, Abstract factory model of the source code here I no longer exemplify the abstract factory source code can be interested in looking for other information

4, summary

The main advantage of the abstract factory pattern is that it isolates the generation of concrete classes, so that customers do not need to know what is being created, and multiple objects in a product family can be created from concrete factory classes at a time. It is convenient to add or replace product families, and it is convenient to add new concrete factories and product families. The main disadvantage is the complexity of adding a new product hierarchy, which requires modification of abstract factories and all concrete factory classes, and skewed support for the “open closed principle”.

The author’s personal summary of the differences between abstract factory and factory methods: Abstract factory pattern has multiple abstract product classes, namely the battery abstract class and the screen abstract class in this example

The abstract factory pattern applies where a system should not depend on the details of how product class instances are created, composed, and expressed; There is more than one product family in the system and only one product family is used at a time; Products belonging to the same product family will be used together; The system provides a library of product classes, all of which appear in the same interface, so that the client is implementation-independent.

The code covered in this section is here

5. Builder mode: Builder mode

1. Schema definition

The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. The Builder pattern, which creates a complex object step by step, allows users to build complex objects simply by specifying their type and content, without needing to know the specific build details inside

Let’s create a popover class in Android for example. This class has the following features:

  • The popover class has two buttons on the left and right, and four properties, title and content, provided by the popover classBuilder classTo assemble themselves.
  • Once the popover is created, the text on the left and right buttons does not want to be changed, so to speakDialogThere is no way to modify the left and right buttons
  • BuilderProvide different creation methods, such as: create a left and right button popover, only a confirm button popover, the creation process is managed by the Builder

The Builder pattern is also easier to understand, as shown in the following code example in Kangkang 5.2

2. Code examples

public class CommonDialog {

    private Params mParams;

    private CommonDialog(Params params) {
        this.mParams = params;
    }

    public void setTitleText(String title) {
        mParams.titleText = title;
    }

    public void setMessageText(String message) {
        mParams.messageText = message;
    }

    public void show(a) {
        //set view...
    }

    public static class Builder {

        protected Params mParams = new Params();

        public Builder setTitleText(String title) {
            mParams.titleText = title;
            return this;
        }

        public Builder setMessageText(String message) {
            mParams.messageText = message;
            return this;
        }

        public Builder setConfirmText(String confirm) {
            mParams.confirmText = confirm;
            return this;
        }

        public Builder setCancelText(String cancel) {
            mParams.cancelText = cancel;
            return this;
        }

        public CommonDialog create(a) {
          	//create normal dialog logic
            return new CommonDialog(mParams);
        }

        public CommonDialog createOnlyConfirm(a) {
          	//create only have confirm btn dialog logic
            mParams.cancelText = null;
            return newCommonDialog(mParams); }}private static class Params {

      	/*public to anyone*/
        private String titleText;
        private String messageText;

      	/*private field , runtime not change*/
        private String confirmText;
        privateString cancelText; }}Copy the code

Example builder pattern usage

public void main(a) {
    CommonDialog.Builder builder = new CommonDialog.Builder();
    builder.setMessageText("will you marry me");
    builder.setConfirmText("yes");
    builder.setCancelText("no");
    CommonDialog normalDialog = builder.create();// Create a normal dialog box
    normalDialog.show();

    builder.setMessageText("are you free now?");
    builder.setConfirmText("yes");
    CommonDialog onlyConfirmDialog = builder.createOnlyConfirm();// Create a confirm button only dialog box
    onlyConfirmDialog.show();
    
    onlyConfirmDialog.setMessageText("Let's go to the movies?");
    onlyConfirmDialog.show();
}
Copy the code

As you can see from the example, when creating a Dialog, you can change any property you want; Once a Dialog instance object has been created, there are few properties that can be modified. Based on this, the author summarizes two characteristics of Builder mode: restriction and encapsulation

3, source anchor point

Builder mode in Android source code implementation: AlertDialog

4, summary

The main advantage of the builder pattern is the client don’t need to know the details of product internal components, the product itself and product creation process decoupling, make the same object creation process can create different products, each specific builders are relatively independent, and have nothing to do with other specific builders, therefore, can be easily replaced builder or add new concrete builders, Compliance with the “open and closed principle” also allows for finer control of the product creation process

Of course, the builder mode also has disadvantages. Its main disadvantages are that the products created by the builder mode generally have more in common and their components are similar, so its use scope is limited to a certain extent, and the corresponding amount of code will increase a lot. If the internal changes of the product are complex, many specific builder classes may need to be defined to implement the changes, resulting in a large system.

The builder model applies to:

  1. The product objects that need to be generated have complex internal structures, often containing multiple member attributes;
  2. The properties of the product objects to be generated depend on each other and the generation sequence needs to be specified.
  3. The creation of an object is independent of the class that created it;
  4. Isolate the creation and use of complex objects and enable the same creation process to create different types of products.

The code covered in this section is here

Six, summarized

This article introduces several common creative design patterns, and briefly summarizes them:

  1. The singleton pattern: Ensures that there is only one instance of a class
    1. Hungry: will take up memory if loaded early
    2. Lazy – Static inner class
    3. Slacker – double check lock DCL
    4. Enumeration singleton: Initialized by the VIRTUAL machine to prevent uniqueness damage
  2. Factory method patternFactory subclasses are used to determine which specific product classes should be instantiated
    1. Simple Factory: A simplified model of the factory method
  3. Abstract Factory pattern: provides an interface for creating a series of related or interdependent objects without specifying their concrete classes
  4. Builder model: Separates the construction of a complex object from its representation so that different representations can be created during the same construction process.

Due to the limited level of the author, it is inevitable that there will be omissions or even errors in the article. If you find any problems or have any suggestions, please refer tohereThank you for submitting the Issue

The full text after

Vii. Reference materials

  • Graphic design patterns
  • “Android source code design mode analysis and combat” – He Honghui/Aixinmin
  • What is the difference between the simple factory pattern, the factory method pattern, and the abstract factory pattern