4. Create model
4.2 factory mode
4.2.1 general
Requirements: design a coffee shop ordering system.
Design a Coffee category and define its two subclasses (American Coffee and latte Coffee); Then design a Coffee store, which has the function of ordering Coffee.
The design of specific classes is as follows:
In java, everything is an object, and these objects need to be created. If we directly new the object when creating it, the object will be seriously coupled. If we want to replace the object, all new objects need to be modified, which obviously violates the opening and closing principle of software design. If we use the factory to produce objects, we can only deal with the factory and completely decouple from the object. If we want to replace the object, we can directly replace the object in the factory to achieve the purpose of decoupling from the object; Therefore, the biggest advantage of factory mode is decoupling.
In this tutorial, we will introduce the use of three factories
- Simple factory mode (23 classic design modes not belonging to GOF)
- Factory method model
- Abstract factory pattern
4.2.2 simple factory mode
Simple factory is not a design pattern, but more like a programming habit.
4.2.2.1 structure
The simple factory contains the following roles:
- Abstract product: it defines the product specification and describes the main characteristics and functions of the product.
- Concrete products: subclasses that implement or inherit Abstract products
- Specific factory: provides a method to create a product, through which the caller obtains the product.
4.2.2.2 realization
Now use a simple factory to improve the above case. The class diagram is as follows:
Factory class codes are as follows:
public class SimpleCoffeeFactory { public Coffee createCoffee(String type) { Coffee coffee = null; if("americano".equals(type)) { coffee = new AmericanoCoffee(); } else if("latte".equals(type)) { coffee = new LatteCoffee(); } return coffee; } }
The factory handles the details of creating objects. Once SimpleCoffeeFactory is available, orderCoffee() in the CoffeeStore class becomes the customer of this object. Later, if you need to obtain the Coffee object directly from the factory. In this way, the coupling with the Coffee implementation class is released, and new coupling is generated, including the coupling between the Coffee store object and the SimpleCoffeeFactory factory object, and the coupling between the factory object and the commodity object.
If new varieties of coffee are added later, we need to modify the code of SimpleCoffeeFactory, which violates the opening and closing principle. There may be many factory class clients, such as creating meituan takeout. In this way, you only need to modify the factory class code and save other modification operations.
4.2.2.4 advantages and disadvantages
advantage:
Encapsulates the process of creating objects, which can be obtained directly through parameters. Separate the object creation from the business logic layer, so as to avoid modifying the customer code in the future. If you want to implement a new product, you can directly modify the factory class without modifying it in the original code, which reduces the possibility of modifying the customer code and makes it easier to expand.
Disadvantages:
When adding new products, you still need to modify the factory code, which violates the "opening and closing principle".
4.2.2.3 extension
Static factory
In the development, some people define the function of creating objects in the factory class as static. This is the static factory pattern, which is not one of the 23 design patterns. The code is as follows:
public class SimpleCoffeeFactory { public static Coffee createCoffee(String type) { Coffee coffee = null; if("americano".equals(type)) { coffee = new AmericanoCoffee(); } else if("latte".equals(type)) { coffee = new LatteCoffee(); } return coffe; } }
4.2.3 factory method mode
In view of the shortcomings in the above example, the factory method mode can be used to solve them perfectly and fully follow the opening and closing principle.
4.2.3.1 concept
Define an interface for creating objects, and let subclasses decide which product class object to instantiate. Factory methods delay instantiation of a product class to subclasses of its factory.
4.2.3.2 structure
The main roles of the factory approach model are:
- Abstract Factory: it provides an interface for creating products, through which callers access factory methods of specific factories to create products.
- Concrete factory: it mainly implements the abstract methods in the abstract factory and completes the creation of specific products.
- Abstract Product: it defines the specification of the Product and describes the main characteristics and functions of the Product.
- Concrete product: it implements the interface defined by the abstract product role, which is created by the specific factory and corresponds to the specific factory one by one.
4.2.3.3 realization
The factory method mode is used to improve the above example. The class diagram is as follows:
The code is as follows:
Abstract factory:
public interface CoffeeFactory { Coffee createCoffee(); }
Specific factory:
public class LatteCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new LatteCoffee(); } } public class AmericanCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new AmericanCoffee(); } }
Coffee shop:
public class CoffeeStore { private CoffeeFactory factory; public CoffeeStore(CoffeeFactory factory) { this.factory = factory; } public Coffee orderCoffee(String type) { Coffee coffee = factory.createCoffee(); coffee.addMilk(); coffee.addsugar(); return coffee; } }
From the code written above, we can see that when adding product classes, we should also add factory classes accordingly. There is no need to modify the code of factory classes, which solves the shortcomings of simple factory mode.
The factory method pattern is a further abstraction of the simple factory pattern. Due to the use of polymorphism, the factory method pattern not only maintains the advantages of the simple factory pattern, but also overcomes its disadvantages.
4.2.3.4 advantages and disadvantages
advantage:
- Users only need to know the name of the specific factory to get the desired product, without knowing the specific creation process of the product;
- When adding new products to the system, only the specific product category and the corresponding specific factory category need to be added without any modification to the original factory, which meets the opening and closing principle;
Disadvantages:
- Every time a product is added, a specific product class and a corresponding specific factory class are added, which increases the complexity of the system.
4.2.4 abstract factory mode
The factory method mode described above considers the production of a class of products, such as only raising animals in livestock farms, only producing televisions in TV factories, and only cultivating students majoring in computer software in intelligence podcasts.
These factories only produce the same kind of products, and the same kind of products are called products of the same grade, that is, the factory method mode only considers the production of products of the same grade, but in real life, many factories are comprehensive factories that can produce multi-level (type) products, such as electric appliance factories that produce both TV sets and washing machines or air conditioners, The university has both software and biology majors.
The abstract factory mode to be introduced in this section will consider the production of multi-level products. A group of products at different levels produced by the same specific factory is called a product family. The horizontal axis shown in the figure below is the product level, that is, the same type of products; The vertical axis is the product family, that is, the products of the same brand, and the products of the same brand are produced in the same factory.
4.2.4.1 concept
It is a pattern structure that provides an interface for the access class to create a group of related or interdependent objects, and the access class can obtain different levels of products of the same family without specifying the specific class of the product.
Abstract factory pattern is an upgraded version of factory method pattern. Factory method pattern only produces one level of products, while abstract factory pattern can produce multiple levels of products.
4.2.4.2 structure
The main roles of the abstract factory pattern are as follows:
- Abstract Factory: it provides an interface for creating products. It contains multiple methods for creating products, and can create multiple products of different levels.
- Concrete Factory: it mainly implements multiple abstract methods in the abstract factory to complete the creation of specific products.
- Abstract Product: it defines the Product specification and describes the main features and functions of the Product. The abstract factory pattern has multiple Abstract products.
- Concrete product: it implements the interface defined by the abstract product role and is created by the concrete factory. It has a many-to-one relationship with the concrete factory.
4.2.4.2 realization
At present, the business of coffee shops has changed, not only to produce coffee, but also to produce desserts, such as tiramisu and Matcha mousse. If you need to define tiramisu, Matcha mousse, tiramisu factory, Matcha mousse factory and dessert factory according to the factory method mode, it is easy to explode. Latte and American coffee are one product grade, both of which are coffee; Tiramisu and Matcha mousse are also a product grade; Latte and tiramisu belong to the same product family (i.e. both belong to Italian flavor), American coffee and Matcha mousse belong to the same product family (i.e. both belong to American flavor). Therefore, this case can be implemented using the abstract factory pattern. Class diagram is as follows:
The code is as follows:
Abstract factory:
public interface DessertFactory { Coffee createCoffee(); Dessert createDessert(); }
Specific factory:
//American dessert factory public class AmericanDessertFactory implements DessertFactory { public Coffee createCoffee() { return new AmericanCoffee(); } public Dessert createDessert() { return new MatchaMousse(); } } //Italian dessert factory public class ItalyDessertFactory implements DessertFactory { public Coffee createCoffee() { return new LatteCoffee(); } public Dessert createDessert() { return new Tiramisu(); } }
If you want to add the same product family, you only need to add another corresponding factory class without modifying other classes.
4.2.4.3 advantages and disadvantages
advantage:
When multiple objects in a product family are designed to work together, it can ensure that the client always uses only the objects in the same product family.
Disadvantages:
When a new product needs to be added to the product family, all factory classes need to be modified.
4.2.4.4 usage scenarios
-
When the objects to be created are a series of interrelated or interdependent product families, such as televisions, washing machines, air conditioners, etc.
-
There are multiple product families in the system, but only one of them is used at a time. If someone only likes to wear clothes and shoes of a certain brand.
-
The class library of products is provided in the system, and the interfaces of all products are the same. The client does not depend on the creation details and internal structure of product instances.
For example, the input method changes the skin, and the whole set changes together. Generate programs for different operating systems.
4.2.5 mode extension
Simple factory + profile decoupling
You can decouple factory objects and product objects by means of factory mode + configuration file. Load the full class name in the configuration file in the factory class and create an object for storage. If the client needs an object, it can get it directly.
Step 1: define the configuration file
For the convenience of demonstration, we use the properties file as the configuration file, named bean.properties
american=com.itheima.pattern.factory.config_factory.AmericanCoffee latte=com.itheima.pattern.factory.config_factory.LatteCoffee
Step 2: improve the factory class
public class CoffeeFactory { private static Map<String,Coffee> map = new HashMap(); static { Properties p = new Properties(); InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties"); try { p.load(is); //Traversing the Properties collection object Set<Object> keys = p.keySet(); for (Object key : keys) { //Get value according to key (full class name) String className = p.getProperty((String) key); //Get bytecode object Class clazz = Class.forName(className); Coffee obj = (Coffee) clazz.newInstance(); map.put((String)key,obj); } } catch (Exception e) { e.printStackTrace(); } } public static Coffee createCoffee(String name) { return map.get(name); } }
The static member variable is used to store the created object (the key stores the name and the value stores the corresponding object), while reading the configuration file and creating the object are written in the static code block, so that it only needs to be executed once.
4.2.6 JDK source code analysis - Collection.iterator method
public class Demo { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("linghu chong"); list.add("Breezy"); list.add("ren woxing"); //Get iterator object Iterator<String> it = list.iterator(); //Traversal using iterators while(it.hasNext()) { String ele = it.next(); System.out.println(ele); } } }
You should be familiar with the above code. Use iterators to traverse the collection and get the elements in the collection. The factory method pattern is used in the method of obtaining iterators from single column sets. Let's look at the structure through the class diagram:
The Collection interface is an abstract factory class, and ArrayList is a concrete factory class; The Iterator interface is an abstract commodity class, and the Iter inner class in the ArrayList class is a concrete commodity class. In the concrete factory class, the iterator() method creates the object of the concrete commodity class.
In addition:
1. The getInstance() method in dateforamt class uses factory mode;
2. The getInstance() method in the calendar class uses factory mode;
4.3 prototype mode
4.3.1 general
Using an instance that has been created as a prototype, create a new object that is the same as the prototype object by copying the prototype object.
4.3.2 structure
The prototype pattern contains the following roles:
- Abstract prototype class: Specifies the clone() method that the concrete prototype object must implement.
- Concrete prototype class: implement the clone() method of the Abstract prototype class, which is an object that can be copied.
- Access class: use the clone() method in the concrete prototype class to copy the new object.
The interface class diagram is as follows:
4.3.3 realization
The cloning of prototype pattern can be divided into shallow cloning and deep cloning.
Shallow cloning: create a new object. The properties of the new object are exactly the same as the original object. For non basic type properties, it still points to the memory address of the object pointed to by the original property.
Deep clone: when a new object is created, other objects referenced in the attribute will also be cloned and no longer point to the original object address.
The clone() method is provided in the Object class in Java to implement shallow cloning. Cloneable interface is the Abstract prototype class in the above class diagram, and the sub implementation class that implements Cloneable interface is the specific prototype class. The code is as follows:
Realizetype (concrete prototype class):
public class Realizetype implements Cloneable { public Realizetype() { System.out.println("The specific prototype object is created!"); } @Override protected Realizetype clone() throws CloneNotSupportedException { System.out.println("Specific prototype copied successfully!"); return (Realizetype) super.clone(); } }
PrototypeTest (test access class):
public class PrototypeTest { public static void main(String[] args) throws CloneNotSupportedException { Realizetype r1 = new Realizetype(); Realizetype r2 = r1.clone(); System.out.println("object r1 and r2 Is it the same object?" + (r1 == r2)); } }
4.3.4 cases
Using prototype mode to generate "three good students" award
The certificates of "three good students" in the same school are the same except for the names of the winners. You can copy multiple certificates of "three good students" in the prototype mode, and then modify the names on the certificates.
Class diagram is as follows:
The code is as follows:
//Certificate of merit public class Citation implements Cloneable { private String name; public void setName(String name) { this.name = name; } public String getName() { return (this.name); } public void show() { System.out.println(name + "Classmate: excellent performance in the first semester of 2020 academic year, and was rated as three good students. This is the case!"); } @Override public Citation clone() throws CloneNotSupportedException { return (Citation) super.clone(); } } //Test access class public class CitationTest { public static void main(String[] args) throws CloneNotSupportedException { Citation c1 = new Citation(); c1.setName("Zhang San"); //Duplicate certificate Citation c2 = c1.clone(); //Change the name of the award to Li Si c2.setName("Li Si"); c1.show(); c2.show(); } }
4.3.5 usage scenarios
- The creation of objects is very complex. You can quickly create objects using prototype mode.
- High performance and safety requirements.
4.3.6 extension (deep cloning)
In the above case of "three good students" award, modify the name attribute of Citation class to Student type attribute. The code is as follows:
//Certificate of merit public class Citation implements Cloneable { private Student stu; public Student getStu() { return stu; } public void setStu(Student stu) { this.stu = stu; } void show() { System.out.println(stu.getName() + "Classmate: excellent performance in the first semester of 2020 academic year, and was rated as three good students. This is the case!"); } @Override public Citation clone() throws CloneNotSupportedException { return (Citation) super.clone(); } } //Student class public class Student { private String name; private String address; public Student(String name, String address) { this.name = name; this.address = address; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } //Test class public class CitationTest { public static void main(String[] args) throws CloneNotSupportedException { Citation c1 = new Citation(); Student stu = new Student("Zhang San", "Xi'an"); c1.setStu(stu); //Duplicate certificate Citation c2 = c1.clone(); //Student object to obtain c2 certificate Student stu1 = c2.getStu(); stu1.setName("Li Si"); //Judge whether stu object and stu1 object are the same object System.out.println("stu and stu1 Is it the same object?" + (stu == stu1)); c1.show(); c2.show(); } }
The operation result is:
explain:
If stu object and stu1 object are the same object, the name attribute value in stu1 object will be changed to "Li Si", and Li Si is displayed in both citation objects. This is the effect of shallow cloning, which copies the reference type attributes in the specific prototype class (Citation). This situation requires deep cloning, which requires object flow. The code is as follows:
public class CitationTest1 { public static void main(String[] args) throws Exception { Citation c1 = new Citation(); Student stu = new Student("Zhang San", "Xi'an"); c1.setStu(stu); //Create object output stream object ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\b.txt")); //Write the c1 object out to a file oos.writeObject(c1); oos.close(); //Create object in and out flow object ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\b.txt")); //read object Citation c2 = (Citation) ois.readObject(); //Student object to obtain c2 certificate Student stu1 = c2.getStu(); stu1.setName("Li Si"); //Judge whether stu object and stu1 object are the same object System.out.println("stu and stu1 Is it the same object?" + (stu == stu1)); c1.show(); c2.show(); } }
The operation result is:
Note: the Citation class and Student class must implement the Serializable interface, otherwise NotSerializableException will be thrown.
4.5 builder mode
4.4.1 general
Separate the construction and representation of a complex object, so that the same construction process can create different representations.
- It separates the construction of components (in the charge of Builder) and assembly (in the charge of Director). Thus, complex objects can be constructed. This pattern is applicable to the complex construction process of an object.
- The decoupling of construction and assembly is realized. Different builders and the same assembly can also make different objects; With the same builder, different assembly sequences can also make different objects. That is to realize the decoupling of construction algorithm and assembly algorithm, and realize better reuse.
- The builder pattern can separate the component from its assembly process and create a complex object step by step. Users only need to specify the type of complex object to get the object without knowing its internal specific construction details.
4.4.2 structure
The Builder pattern contains the following roles:
-
Abstract Builder class: this interface specifies the creation of those parts of complex objects, and does not involve the creation of specific component objects.
-
Concrete Builder class: it implements the Builder interface and completes the specific creation method of various components of complex products. After the construction process is completed, an example of the product is provided.
-
Product: complex object to create.
-
Director: call the specific builder to create each part of the complex object. The director does not involve the information of the specific product, but is only responsible for ensuring that each part of the object is created completely or in a certain order.
Class diagram is as follows:
4.4.3 examples
Create shared bike
The production of bicycle is a complex process, which includes the production of frame, seat and other components. The frame is made of carbon fiber, aluminum alloy and other materials, and the seat is made of rubber, leather and other materials. For the production of bicycles, the builder model can be used.
Here Bike is a product, including frame, seat and other components; Builder is an abstract builder, MobikeBuilder and OfoBuilder are concrete builders; Director is the commander. Class diagram is as follows:
The specific codes are as follows:
//Bicycles public class Bike { private String frame; private String seat; public String getFrame() { return frame; } public void setFrame(String frame) { this.frame = frame; } public String getSeat() { return seat; } public void setSeat(String seat) { this.seat = seat; } } // Abstract builder class public abstract class Builder { protected Bike mBike = new Bike(); public abstract void buildFrame(); public abstract void buildSeat(); public abstract Bike createBike(); } //Moby bike Builder class public class MobikeBuilder extends Builder { @Override public void buildFrame() { mBike.setFrame("Aluminum alloy frame"); } @Override public void buildSeat() { mBike.setSeat("Leather seat"); } @Override public Bike createBike() { return mBike; } } //ofo Builder class public class OfoBuilder extends Builder { @Override public void buildFrame() { mBike.setFrame("Carbon fiber frame"); } @Override public void buildSeat() { mBike.setSeat("Rubber seat"); } @Override public Bike createBike() { return mBike; } } //Conductor class public class Director { private Builder mBuilder; public Director(Builder builder) { mBuilder = builder; } public Bike construct() { mBuilder.buildFrame(); mBuilder.buildSeat(); return mBuilder.createBike(); } } //Test class public class Client { public static void main(String[] args) { showBike(new OfoBuilder()); showBike(new MobikeBuilder()); } private static void showBike(Builder builder) { Director director = new Director(builder); Bike bike = director.construct(); System.out.println(bike.getFrame()); System.out.println(bike.getSeat()); } }
be careful:
The above example is the general usage of the Builder mode. The commander class Director plays a very important role in the Builder mode. It is used to guide the specific Builder how to build the product, control the call order, and return the complete product class to the caller. However, in some cases, the system structure needs to be simplified, and the commander class can be combined with the abstract Builder
// Abstract builder class public abstract class Builder { protected Bike mBike = new Bike(); public abstract void buildFrame(); public abstract void buildSeat(); public abstract Bike createBike(); public Bike construct() { this.buildFrame(); this.BuildSeat(); return this.createBike(); } }
explain:
This does simplify the system structure, but it also aggravates the responsibilities of the abstract builder class, and does not conform to the principle of single responsibility. If the construct() is too complex, it is recommended to package it into the Director.
4.4.4 advantages and disadvantages
advantage:
- The builder model is well encapsulated. Using the builder mode can effectively encapsulate changes. In the scenario of using the builder mode, the general product class and builder class are relatively stable. Therefore, encapsulating the main business logic in the commander class can achieve better stability as a whole.
- In the builder mode, the client does not need to know the details of the internal composition of the product, and decouples the product itself from the product creation process, so that the same creation process can create different product objects.
- You can more finely control the product creation process. The creation steps of complex products are decomposed into different methods, which makes the creation process clearer and easier to use programs to control the creation process.
- The builder pattern is easy to extend. If there are new requirements, it can be completed by implementing a new builder class. Basically, there is no need to modify the previously tested code, so there will be no risk to the original functions. Comply with the opening and closing principle.
Disadvantages:
The products created by Creator mode generally have more in common and their components are similar. If there are great differences between products, it is not suitable to use builder mode, so its scope of use is limited.
4.4.5 usage scenarios
The Builder pattern creates complex objects. Each part of its product is often faced with drastic changes, but the algorithm that combines them is relatively stable, so it is usually used in the following occasions.
- The created object is complex and consists of multiple components. Each component faces complex changes, but the construction sequence between components is stable.
- The algorithm of creating a complex object is independent of the components of the object and their assembly mode, that is, the construction process and final representation of the product are independent.
4.4.6 mode extension
In addition to the above purposes, the builder mode is also commonly used in development. When a class constructor needs to pass in many parameters, if an instance of this class is created, the code readability will be very poor, and it is easy to introduce errors. At this time, the builder mode can be used for reconstruction.
The code before refactoring is as follows:
public class Phone { private String cpu; private String screen; private String memory; private String mainboard; public Phone(String cpu, String screen, String memory, String mainboard) { this.cpu = cpu; this.screen = screen; this.memory = memory; this.mainboard = mainboard; } public String getCpu() { return cpu; } public void setCpu(String cpu) { this.cpu = cpu; } public String getScreen() { return screen; } public void setScreen(String screen) { this.screen = screen; } public String getMemory() { return memory; } public void setMemory(String memory) { this.memory = memory; } public String getMainboard() { return mainboard; } public void setMainboard(String mainboard) { this.mainboard = mainboard; } @Override public String toString() { return "Phone{" + "cpu='" + cpu + '\'' + ", screen='" + screen + '\'' + ", memory='" + memory + '\'' + ", mainboard='" + mainboard + '\'' + '}'; } } public class Client { public static void main(String[] args) { //Building Phone objects Phone phone = new Phone("intel","Samsung screen","Kingston","ASUS"); System.out.println(phone); } }
The Phone object built in the client code above passes four parameters. What if there are more parameters? The readability of the code and the cost of using it are relatively high.
Refactored Code:
public class Phone { private String cpu; private String screen; private String memory; private String mainboard; private Phone(Builder builder) { cpu = builder.cpu; screen = builder.screen; memory = builder.memory; mainboard = builder.mainboard; } public static final class Builder { private String cpu; private String screen; private String memory; private String mainboard; public Builder() {} public Builder cpu(String val) { cpu = val; return this; } public Builder screen(String val) { screen = val; return this; } public Builder memory(String val) { memory = val; return this; } public Builder mainboard(String val) { mainboard = val; return this; } public Phone build() { return new Phone(this);} } @Override public String toString() { return "Phone{" + "cpu='" + cpu + '\'' + ", screen='" + screen + '\'' + ", memory='" + memory + '\'' + ", mainboard='" + mainboard + '\'' + '}'; } } public class Client { public static void main(String[] args) { Phone phone = new Phone.Builder() .cpu("intel") .mainboard("ASUS") .memory("Kingston") .screen("Samsung") .build(); System.out.println(phone); } }
The refactored code is more convenient to use and can improve the development efficiency to some extent. In terms of software design, the requirements for programmers are relatively high.
4.6 comparison of Creator modes
4.6.1 factory method mode VS builder mode
The factory method pattern focuses on the creation of the overall object; The builder pattern focuses on the process of component construction, which is intended to create a complex object through precise construction step by step.
Let's give a simple example to illustrate the difference between the two. If we want to create a superman, if we use the factory method mode, we will directly produce a Superman with infinite power, flying and wearing underwear; If you use the builder mode, you need to assemble the hands, head, feet, trunk and other parts, and then wear your underwear outside, so a superman was born.
4.6.2 abstract factory mode VS builder mode
Abstract factory mode realizes the creation of product families. A product family is a series of products: product combinations with different classification dimensions. Adopting abstract factory mode does not need to care about the construction process, but only about what products are produced by what factory.
The builder model requires the product to be built according to the specified blueprint. Its main purpose is to produce a new product by assembling spare parts.
If the abstract factory pattern is regarded as an auto parts production factory to produce the products of a product family, the builder pattern is an auto assembly factory, which can return a complete car through the assembly of parts.