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:
- The singleton pattern
- The factory pattern
- 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:
-
Enumeration classes cannot be created by reflection
Forced to use reflection to create will receive error: Java. Lang. IllegalArgumentException: always reflectively create enum objects
-
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:
Abstract product
Is 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;Specific products
Abstract product interfaces are implemented, and concrete products of a certain type are created by specific concrete factories, often corresponding to each other.The abstract factory
It is the core of the factory method pattern. Any factory class that creates objects in the pattern must implement this interface.The specific factory
Is 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:
- The abstract factory
- 可迭代
- Factory implementation
- ArrayList
- HashMap
- Abstract product
- Iterator
- Product realization(jdk1.8)
- Itr class in ArrayList
- 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:
The abstract factory
Methods used to declare the generation of abstract products;The specific factory
Abstract 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;Abstract product
Declare interfaces for each product and define abstract business methods for the product in the abstract product;Specific products
Define 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:
- Abstract product
- Battery assembly plant
- Screen assembly plant
- Specific product realization
- The battery
- Byd Battery Supplier
- Desai battery supplier
- The screen
- Boe screen supplier
- LG Screen Supplier
- The battery
- The abstract factory
- Mobile phone assembly plant
- Specific factory implementation
- Huawei P50 assembly plant
- 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 class
Builder class
To assemble themselves. - Once the popover is created, the text on the left and right buttons does not want to be changed, so to speak
Dialog
There is no way to modify the left and right buttons Builder
Provide 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:
- The product objects that need to be generated have complex internal structures, often containing multiple member attributes;
- The properties of the product objects to be generated depend on each other and the generation sequence needs to be specified.
- The creation of an object is independent of the class that created it;
- 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:
The singleton pattern
: Ensures that there is only one instance of a class- Hungry: will take up memory if loaded early
- Lazy – Static inner class
- Slacker – double check lock DCL
- Enumeration singleton: Initialized by the VIRTUAL machine to prevent uniqueness damage
Factory method pattern
Factory subclasses are used to determine which specific product classes should be instantiated- Simple Factory: A simplified model of the factory method
Abstract Factory pattern
: provides an interface for creating a series of related or interdependent objects without specifying their concrete classesBuilder 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