Design pattern related content
Design pattern classification
Create pattern
It is used to describe "how to create objects". Its main feature is to separate the creation and use of objects.
GOF book provides five creation modes: singleton, prototype, factory method, abstract factory and builder
Structural model
Used to describe how classes or objects form a larger structure according to a certain layout.
GOF book provides 7 structural modes, such as agent, adapter, bridge, decoration, appearance, element sharing and combination
Behavioral model
It is used to describe how classes or objects cooperate with each other to complete tasks that cannot be completed by a single object, and how to allocate responsibilities.
GOF book provides 11 behavioral models, including template method, strategy, command, responsibility chain, state, observer, mediator, iterator, visitor, memo and interpreter
UML diagram
Unified modeling language is a visual modeling language used to design software. Its characteristic is simple, unified, graphical, and can express the dynamic and static information in software design
Symbol for visibility
+: indicates public
-: indicates private
#: indicates protected
Full representation of attribute: visibility Name: type [= Default]
Full representation of method: visibility name (parameter list) [: return value]
Relationship between classes
Association relationship
Association relationship is a reference relationship between objects. It is used to represent the relationship between one kind of objects and another kind of objects. It is the most commonly used relationship between classes. It is divided into general association relationship, aggregation relationship and combination relationship
Association relationship can be divided into one-way Association, two-way Association and self association
Aggregation relation
Aggregation relationship is a kind of association relationship. It is a strong association relationship and a relationship between whole and part
The aggregation relationship is also realized through the member object, in which the member object is a part of the whole, but the member object can exist independently from the whole object, such as schools and teachers. If the school is gone, teachers can go to other schools
synthetic relation
Composition represents the whole part relationship between classes, but it is a stronger aggregation relationship
In the combination relationship, the whole object can control the life cycle of some objects. Once the whole object does not exist, some objects will not exist, and some objects cannot exist without the whole object. For example, in the relationship between head and mouth, if the head is gone, the mouth will not exist
Dependency
Dependency is a kind of usage relationship. It is an association mode with the weakest coupling between objects. It is a temporary association. In the code, the method of a class accesses some methods in another class (dependent class) through local, method parameters or calls to static methods to complete some responsibilities
Inheritance relationship
Inheritance relationship is the most coupling relationship between objects, which represents the relationship between general and special. It is the relationship between parent and child classes. It is an inheritance relationship
Implementation relationship
Implementation relationship is the relationship between an interface and an implementation class. In this relationship, the class implements the interface, and the operations in the class implement all the abstract methods declared in the interface
Software design principles
Opening and closing principle
It is open to extensions and closed to modifications.
When the program needs to be extended, you can't modify the original code to achieve a hot plug effect. In short, it is to make the program scalable and easy to maintain and upgrade
Richter substitution principle
Wherever a parent class can appear, a child class must appear.
When a subclass inherits from the parent class, try not to override the methods of the parent class except adding new methods to complete the new functions
Dependence Inversion Principle
High level modules should not rely on low-level modules, and both should rely on their abstraction; Abstract should not rely on details, details should rely on abstraction.
In short, it requires programming the abstraction rather than the implementation, which reduces the coupling between the customer and the implementation module
Interface isolation principle
The client should not be forced to rely on methods it does not use; The dependence of one class on another should be based on the smallest interface
Dimitt's law
Dimitri's law is also called the principle of least knowledge
If the two software entities do not need to communicate directly, there should be no direct related call, and the call can be forwarded with the third party of the Forbidden City. Its purpose is to reduce the coupling between classes and improve the mutual independence of modules
Principle of synthetic administration
Try to use association relations such as composition or aggregation first, and then consider using inheritance relations
Creator mode (5)
Singleton mode
This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique object, which can be accessed directly without instantiating the object of this class
Hungry man model
Each object is initialized before it is used. This can lead to potential performance problems: what if the object is large? Loading this object into memory without using it is a huge waste
public final class EagerSingleton { private static EagerSingleton singObj = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getSingleInstance() { return singObj; } }
Lazy mode
It uses delayed loading to ensure that objects will not be initialized before they are used. However, usually at this time, the interviewer will ask new questions to make things difficult. He would ask: is this writing thread safe? The answer must be: unsafe
public final class LazySingleton { private static LazySingleton singObj = null; private LazySingleton() { } public static LazySingleton getSingleInstance() { if (null == singObj ) { singObj = new LazySingleton(); } return singObj; } }
Double checked lock
Double check lock mode is a very good singleton implementation mode, which solves the problems of singleton, performance and thread safety. In addition, volatile keyword is added to prevent instruction rearrangement at the bottom of JVM, which perfectly realizes the singleton mode
public final class DoubleCheckedSingleton { //volatile prevents JVM instruction rearrangement private static volatile DoubleCheckedSingletonsingObj = null; private DoubleCheckedSingleton() { } // Provide a static method to get the object public static DoubleCheckedSingleton getSingleInstance() { // For the first time, if instance is not null, it will not enter the lock grabbing phase and return the instance directly if (null == singObj ) { Synchronized(DoubleCheckedSingleton.class) { // After grabbing the lock, judge whether it is null again if (null == singObj) { singObj = new DoubleCheckedSingleton(); } } } return singObj; } }
Static inner class
Here we will propose a new mode - Initialization on Demand Holder This method uses internal classes to delay loading objects. When initializing this internal class, JLS(Java Language Sepcification) will ensure the thread safety of this class. The greatest beauty of this writing method is that it completely uses the mechanism of Java virtual machine to ensure synchronization, and there is no synchronization keyword
public class Singleton { // Private construction method private Singleton(){} // Define a static inner class private static class SingletonHolder { public final static Singleton instance = new Singleton(); } // Provide public access public static Singleton getInstance() { return SingletonHolder.instance; } }
The instance will not be initialized when the Singleton class is loaded for the first time. Only getInstance is called for the first time. The virtual machine loads the SingletonHolder and initializes the instance, which can not only ensure thread safety, but also ensure the uniqueness of the Singleton class
Static inner class singleton mode is an excellent singleton mode, which is commonly used in open source projects. Without any locking, it ensures the safety of multithreading without any performance impact and waste of space
Enumeration mode
The enumeration class implementation singleton mode is highly recommended because the enumeration type is thread safe and can only be loaded once. The designer makes full use of the particularity of enumeration to implement the singleton mode. The writing method of enumeration is very simple, and the enumeration type is the only singleton implementation mode that will not be destroyed
public enum Singleton{ INSTANCE; }
Destroy singleton mode
There are two ways to break the singleton pattern: serialization and reflection
Serialization breaks the singleton pattern
// Serialization breaking singleton mode for testing public class Test { public static void main(String[] args) throws Exception { writeObject2File(); // The object addresses read twice are different, which successfully destroys the singleton mode readObjectFromFile(); readObjectFromFile(); } // Read data from file (object) public static void readObjectFromFile() throws Exception { // Create an input stream object ObjectInputStream ois = new ObjectInputStream( new FileInputStream("file path")); // Read objects in file Singleton instance = (Singleton)ois.readObject(); System.out.println(instance); // Release resources ois.close(); } // Write data (objects) to a file public static void writeObject2File() throws Exception { // Get Singleton object Singleton instance = new Singleton(); // Create object output stream object ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("file path")); // Write object to file oos.writeObject(instance); // Release resources oos.close(); } }
terms of settlement
Add the readResolve() method to the Singleton class. It is called by reflection during deserialization. If this method is defined, it returns the value of this method. If it is not defined, it returns the new object
public class Singleton { // Private construction method private Singleton(){} // Define a static inner class private static class SingletonHolder { public final static Singleton instance = new Singleton(); } // Provide public access public static Singleton getInstance() { return SingletonHolder.instance; } // When deserializing, the method will be called automatically and the return value of the method will be returned directly public Object readResolve(){ return SingletonHolder.instance; } }
The reflection mode destroys the singleton mode
// Reflection destruction singleton mode for test public class Test2 { public static void main(String[] args) { // Gets the bytecode object of Singleton Class<Singleton> clazz = Singleton.class; // Get parameterless constructor Constructor cons = clazz.getDeclaredConstructor(); // Cancel access check cons.setAccessible(true); // create object Singleton s1 = (Singleton)cons.newInstance(); Singleton s2 = (Singleton)cons.newInstance(); // The output is false, indicating that the singleton mode is broken System.out.println(s1 == s2); } }
terms of settlement
Add judgment in the constructor. If the constructor is called multiple times to create an object, a runtime exception will be thrown
public class Singleton { private static boolean flag = false; // Private construction method private Singleton(){ synchronized (Singleton.class){ // Judge whether the value of flag is true // true indicates a non first access // false indicates the first visit if(flag){ throw new RuntimeException("Cannot create multiple objects"); } // Set flag to true flag = true; } } // Define a static inner class private static class SingletonHolder { public final static Singleton instance = new Singleton(); } // Provide public access public static Singleton getInstance() { return SingletonHolder.instance; } // When deserializing, the method will be called automatically and the return value of the method will be returned directly public Object readResolve(){ return SingletonHolder.instance; } }
JDK source code analysis - Runtime
The Runtime class in the JDK source code is the singleton pattern used
Prototype mode
summary
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
structure
Abstract prototype class: Specifies the clone method that the concrete prototype object must implement
Concrete prototype class: the clone method that implements 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 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 those of 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 provided in the Object class in java is used to realize shallow cloning. Clonable interface is the Abstract prototype class in the above class diagram, and the sub implementation class implementing clonebale interface is the specific prototype class
Code example
// Concrete prototype class public class Realizetype implements Cloneable{ public Realizetype(){ System.out.println("The specific prototype object is created"); } @Override protected Object clone() throws CloneNotSupportedException { System.out.println("The specific prototype was copied successfully"); return super.clone(); } } //test public class Client { public static void main(String[] args) throws Exception{ //Create a prototype class object Realizetype realizetype = new Realizetype(); //Clone the object by calling the clone method in the Realizetype class Realizetype clone = (Realizetype)realizetype.clone(); System.out.println(realizetype==clone); } }
case
Using prototype mode to generate "three good students" award certificate
//Certificate of merit public class Citation implements Cloneable{ //Names on three good students private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } public void show(){ System.out.println(name+"Classmate: get a certificate"); } @Override protected Citation clone() throws CloneNotSupportedException { return (Citation)super.clone(); } } //test public class Client { public static void main(String[] args) throws Exception{ //Create a prototype class object Citation citation = new Citation(); //Clone award object Citation clone = (Citation)citation.clone(); citation.setName("zhangsan"); clone.setName("lisi"); citation.show(); clone.show(); } }
Prototype mode usage scenario
- The creation of objects is very complex. You can quickly create objects using prototype mode
- High performance and safety requirements
Extended (deep clone)
Deep cloning using object flow
//Student class, in order to show deep cloning public class Student implements Serializable { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } } //Certificate of merit public class Citation implements Cloneable, Serializable { //Names on three good students private Student student; public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } public void show(){ System.out.println(student.getName()+"Classmate: get a certificate"); } @Override protected Citation clone() throws CloneNotSupportedException { return (Citation)super.clone(); } } //test public class Client { public static void main(String[] args) throws Exception{ //Create a prototype class object Citation citation = new Citation(); Student student = new Student(); student.setName("zhangsan"); citation.setStudent(student); //Create a stream output stream object for the object ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/test.txt")); //Write object to file oos.writeObject(citation); //Release resources oos.close(); //Create an input stream object for the object ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/test.txt")); //Read object from file Citation citation1 = (Citation)ois.readObject(); //Release resources ois.close(); citation1.getStudent().setName("lisi"); citation.show(); citation1.show(); } }
Note: for deep cloning using object stream, the related bean s should implement the serialization interface
Factory method model
The biggest advantage of factory mode is decoupling
The unchanged sample code used in the following factory methods
// Coffee public abstract class Coffee { // Add sugar public void addSuger(){ System.out.println("Add sugar"); } // Add milk public void addMilk(){ System.out.println("Add milk"); } // Coffee name public abstract String getName(); } //Cafe Latte public class LatteCoffee extends Coffee{ // name @Override public String getName() { return "Cafe Latte"; } } //Cafe Americano public class AmericanCoffee extends Coffee{ // name @Override public String getName() { return "Cafe Americano"; } }
Simple factory mode
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
Code example
// Simple coffee factory, producing coffee public class SimpleCoffeeFactory { public Coffee createCoffee(String type){ //Declare variables of Coffee type and create different Coffee subclass objects according to different types Coffee coffee = null; if ("american".equals(type)) { coffee = new AmericanCoffee(); } else if ("latte".equals(type)){ coffee = new LatteCoffee(); } else { throw new RuntimeException("No,"); } return coffee; } } // coffee shop public class CoffeeStore { public Coffee orderCoffee(String type){ // Create a simple factory SimpleCoffeeFactory simpleCoffeeFactory = new SimpleCoffeeFactory(); // Call the factory to produce coffee Coffee coffee = simpleCoffeeFactory.createCoffee("latte"); // Add ingredients coffee.addMilk(); coffee.addSuger(); return coffee; } } // Test class public class Client { public static void main(String[] args) { //Create coffee shop class CoffeeStore coffeeStore = new CoffeeStore(); //order Coffee coffee = coffeeStore.orderCoffee("latte"); System.out.println(coffee.getName()); } }
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 source 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"
Expansion - static factory
// Simple coffee factory, producing coffee public class SimpleCoffeeFactory { public static Coffee createCoffee(String type){ //Declare variables of Coffee type and create different Coffee subclass objects according to different types Coffee coffee = null; if ("american".equals(type)) { coffee = new AmericanCoffee(); } else if ("latte".equals(type)){ coffee = new LatteCoffee(); } else { throw new RuntimeException("No,"); } return coffee; } }
Factory method model
For the shortcomings of simple factory, the factory method mode can be perfectly solved and fully abide by the opening and closing principle
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
Structure:
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 realizes the abstract methods in the abstract factory and completes the creation of specific products
Abstract product: it defines the product specification and describes the main characteristics and functions of the product
Concrete product: it implements the interface defined by the abstract product role lock, which is created by the concrete factory and corresponds to the concrete factory one by one
Code example
//Abstract factory public interface CoffeeFactory { //Method of creating coffee object Coffee createCoffee(); } //American coffee factory, specializing in the production of American coffee public class AmericanCoffeeFactory implements CoffeeFactory{ @Override public Coffee createCoffee() { return new AmericanCoffee(); } } //Latte coffee factory, specializing in the production of latte coffee public class LatteCoffeeFactory implements CoffeeFactory{ @Override public Coffee createCoffee() { return new LatteCoffee(); } } // coffee shop public class CoffeeStore { private CoffeeFactory factory; public void setFactory(CoffeeFactory factory){ this.factory = factory; } public Coffee orderCoffee(){ // Call the factory to produce coffee Coffee coffee = factory.createCoffee(); // Add ingredients coffee.addMilk(); coffee.addSuger(); return coffee; } } //test public class Client { public static void main(String[] args) { //Create coffee shop object CoffeeStore store = new CoffeeStore(); //Create factory AmericanCoffeeFactory factory = new AmericanCoffeeFactory(); store.setFactory(factory); //Order coffee Coffee coffee = store.orderCoffee(); System.out.println(coffee.getName()); } }
advantage:
Users only need to know the name of the specific factory to get the required products, without knowing the specific creation process of the products
When a new product is added 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
Abstract factory pattern
Concept:
It is a pattern structure that provides an interface for an access class to create a group of related or interdependent objects, and the access class can obtain different products of the same family without specifying the specific class of the product
Abstract factory mode is an upgraded version of factory method mode. Factory method mode only produces one level of products, while abstract factory mode can produce multiple levels of products
Structure:
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 characteristics 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 lock, which is created by the concrete factory. It has a many-to-one relationship with the concrete factory
Code example
//Dessert abstract class public abstract class Dessert { public abstract void show(); } //Tiramisu public class Trimisu extends Dessert { @Override public void show() { System.out.println("Tiramisu"); } } //Matcha Mousse public class MatchaMousse extends Dessert{ @Override public void show() { System.out.println("Matcha Mousse"); } } //Abstract factory public interface DessertFactory { //Produce coffee Coffee createCoffee(); //Produce dessert Dessert createDessert(); } //American dessert factory public class AmericanDessertFactory implements DessertFactory { //Cafe Americano @Override public Coffee createCoffee() { return new AmericanCoffee(); } //Matcha Mousse @Override public Dessert createDessert() { return new MatchaMousse(); } } //Italian dessert factory public class ItalyDessertFactory implements DessertFactory { //Cafe Latte @Override public Coffee createCoffee() { return new LatteCoffee(); } //Tiramisu @Override public Dessert createDessert() { return new Trimisu(); } } //test public class client { public static void main(String[] args) { //Created is an Italian dessert factory object ItalyDessertFactory factory = new ItalyDessertFactory(); //Get latte and tiramisu Coffee coffee = factory.createCoffee(); Dessert dessert = factory.createDessert(); System.out.println(coffee.getName()); dessert.show(); } }
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
shortcoming
When a new product needs to be added to the product family, all factory classes need to be modified
Mode extension
Simple factory + profile decoupling
Factory objects and products can be decoupled through 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.hupf.study.demo1.AmericanCoffee latte=com.hupf.study.demo1.LatteCoffee
Step 2: improve the factory class
public class CoffeeFactory { //Load the configuration file, obtain the full class name configured in the configuration file, and create the object of this class for storage //Define container objects to store coffee objects private static Map<String, Coffee> map = new HashMap(); static{ //Load profile 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 aClass = Class.forName(className); Coffee obj = (Coffee)aClass.newInstance(); map.put((String)key,obj); } } catch (Exception e){ e.printStackTrace(); } } //Get object by name public static Coffee createCoffee(String name){ return map.get(name); } } //test public class client { public static void main(String[] args) { Coffee american = CoffeeFactory.createCoffee("american"); System.out.println(american.getName()); } }
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
JDK source code analysis - collection Iterator method
Builder pattern
summary
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; From the same construction, 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
structure
Abstract Builder class: this interface specifies the creation of those parts of complex objects, and does not involve the creation of specific object parts
Concrete Builder class: it implements the Builder interface and completes the specific creation method of various components of complex products. After the reconstruction process, provide an example of the product
Product: complex object to create
Director: call the specific builder to create each part of the complex object. The commander does not involve the information of specific products, but is only responsible for ensuring that each part of the object is created completely or in a certain order
Create a bike, code example
// Product category, bicycle public class Bike { private String frame;//Frame private String seat;//Car 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; } @Override public String toString() { return "Bike{" + "frame='" + frame + '\'' + ", seat='" + seat + '\'' + '}'; } } //Specific Builder: build worship bike public class MobileBuilder extends Builder { @Override public void buildFrame() { bike.setFrame("Carbon fiber frame"); } @Override public void buildSeat() { bike.setSeat("Leather seat"); } @Override public Bike createBike() { return bike; } } //Specific Builder: build ofo public class OfoBuilder extends Builder{ @Override public void buildFrame() { bike.setFrame("Aluminum alloy frame"); } @Override public void buildSeat() { bike.setSeat("Rubber seat"); } @Override public Bike createBike() { return bike; } } //Abstract builder public abstract class Builder { //Declare a variable of type Bike and assign a value protected Bike bike = new Bike(); //Build frame public abstract void buildFrame(); //Building a seat public abstract void buildSeat(); //Build a bike public abstract Bike createBike(); } //Commander public class Director { //Declare variables of type builder private Builder builder; public Director(Builder builder){ this.builder=builder; } //Method of assembling bicycle public Bike construct(){ builder.buildFrame(); builder.buildSeat(); return builder.createBike(); } } //test public class Client { public static void main(String[] args) { //Create commander object Director director = new Director(new MobileBuilder()); //Let the conductor direct the assembly of bicycles Bike bike = director.construct(); System.out.println(bike.getFrame()); System.out.println(bike.getSeat()); } }
be careful
It can simplify the system structure and combine the commander class with the abstract builder
//Abstract builder public abstract class Builder { //Declare a variable of type Bike and assign a value protected Bike bike = new Bike(); //Build frame public abstract void buildFrame(); //Building a seat public abstract void buildSeat(); //Build a bike public abstract Bike createBike(); //Method of assembling bicycle public Bike construct(){ builder.buildFrame(); builder.buildSeat(); return builder.createBike(); } }
explain
This does simplify the system construction, but at the same time, it also increases the responsibilities of the abstract builder class, which is not in line with the principle of single responsibility. If the commander's assembly method is too complex, it is recommended to package it into the commander
advantage
- The builder model has good encapsulation. 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 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, 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 creation process. The complex creation steps 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 the new requirements can be completed by implementing a new builder class, there is basically no need to modify the tested code, so there will be no risk to the original functions. Comply with the opening and closing principle
shortcoming
The products created by the builder mode generally have more in common and their components are similar. If there are great differences between products, the builder mode is not suitable for use, so its scope of use is limited
Usage scenario
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 has complex changes, but the earlier order between components is stable
- The algorithm of creating a complex object is independent of the components of the object and their assembly method, that is, the construction process and final representation of the product are independent
Mode extension
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 pattern can be used for reconstruction
public class Phone { private String cpu; private String screen; private String memory; private String mainborad; private Phone(Builder builder) { this.cpu=builder.cpu; this.screen=builder.screen; this.memory=builder.memory; this.mainborad=builder.mainborad; } public static final class Builder{ private String cpu; private String screen; private String memory; private String mainborad; public Builder cpu(String cpu){ this.cpu = cpu; return this; } public Builder screen(String screen){ this.screen = screen; return this; } public Builder memory(String memory){ this.memory = memory; return this; } public Builder mainborad(String mainborad){ this.mainborad = mainborad; return this; } //Create a phone object using the builder public Phone build(){ return new Phone(this); } } @Override public String toString() { return "Phone{" + "cpu='" + cpu + '\'' + ", screen='" + screen + '\'' + ", memory='" + memory + '\'' + ", mainborad='" + mainborad + '\'' + '}'; } } //test public class Client { public static void main(String[] args) { Phone phone = new Phone.Builder() .cpu("cpu") .mainborad("mainborad") .memory("memory") .screen("screen") .build(); System.out.println(phone.toString()); } }
Summary (creator mode comparison)
Factory method mode VS builder mode
The factory method pattern focuses on the creation of the overall object; The builder model focuses on the process of component construction, which is intended to create a complex object through step-by-step precise construction
Let's give a simple example to illustrate the difference between the two. If we make a superman, if we use the factory method mode, we will directly produce a Superman with infinite power, capable of flying and wearing underwear; If you use the builder mode, you need to assemble the hands, feet, trunk and other parts, and then wear your underwear outside, so a superman was born
Abstract factory pattern VS builder pattern
Abstract factory can realize the creation of product family. 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. His 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
Structural mode (7 kinds)
Structural pattern describes how to form a larger structure of classes or objects according to a certain layout. It is divided into class structural pattern and object structural pattern. The former uses inheritance mechanism to organize interfaces and classes, and the latter uses composition or aggregation to form objects
Because the coupling degree of combination relationship or aggregation relationship is lower than that of inheritance relationship and meets the "composition principle", the object structural pattern has more flexibility than the class structural pattern
The structural mode is divided into the following 7 types:
- proxy pattern
- Adapter mode
- Decorative pattern
- Bridging mode
- Appearance mode
- Combination mode
- Sharing element mode
proxy pattern
summary
For some reason, an object needs to be provided with a proxy to control access to the object. At this time, the access object is not suitable or can not directly reference the target object. The proxy object acts as an intermediary between the access object and the target object
According to the different generation time of proxy class, proxy in java is divided into static proxy and dynamic proxy.
Static proxy classes are generated at compile time
Dynamic proxy classes are dynamically generated by Java runtime. Dynamic agents include JDK agent and CGLib agent
structure
Abstract topic class (Subject): declare the business methods implemented by real topics and proxy objects through interfaces or abstract classes
Real Subject: it realizes the specific business in the abstract subject. The real object represented by the time management object is the object to be referenced finally
proxy class: it provides the same interface as the real topic. It contains references to the real topic. It can access, control or extend the functions of the real topic
Static proxy
Examples of ticket sales at railway stations
//Interface for selling train tickets public interface SellTickets { public void sell(); } //Train station ticket public class TrainStation implements SellTickets{ @Override public void sell() { System.out.println("Train station ticket"); } } //Ticket selling point public class ProxyPoint implements SellTickets{ //Declare railway station class object private TrainStation trainStation=new TrainStation(); @Override public void sell() { System.out.println("Service fees are charged at the consignment point"); trainStation.sell(); } } public class Client { public static void main(String[] args) { //Create consignment point class object ProxyPoint proxyPoint = new ProxyPoint(); //Selling tickets proxyPoint.sell(); } }
JDK dynamic agent
Java provides a dynamic proxy class proxy. Proxy is not the class of proxy object mentioned above, but provides a static method (newProxyInstance method) to create an object to obtain the proxy object
//Interface for selling train tickets public interface SellTickets { public void sell(); } //Train station ticket public class TrainStation implements SellTickets{ @Override public void sell() { System.out.println("Train station ticket"); } } //Gets the factory class of the proxy object public class ProxyFactory { //Declare target object private TrainStation station = new TrainStation(); //Method to get proxy object public SellTickets getProxyObject(){ //Return proxy object /** * classLoader:Class loader, which is used to load proxy classes. The class loader can be obtained through the target object * Class<?>[] interfaces:Bytecode object of the interface implemented by the proxy class * InvocationHandler:Call handler for proxy object */ SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance( station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() { /** * * @param proxy: Proxy object. It is the same object as proxyObject object and is basically not used in the invoke method * @param method: method object that encapsulates the methods in the interface * @param args: Actual parameters of the calling method */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //Agent class enhancements System.out.println("Service charge for agency"); //Method of executing target object Object invoke = method.invoke(station, args); return invoke; } } ); return proxyObject; } } //test public class Client { public static void main(String[] args) { //Get proxy object //Create proxy factory object ProxyFactory proxyFactory = new ProxyFactory(); //Get the proxy object using the method of the factory object SellTickets sellTickets = proxyFactory.getProxyObject(); //Calling real object methods sellTickets.sell(); } }
Execution process
- Call the sell method through the proxy object in the test class
- According to the characteristics of polymorphism, the sell method in the Proxy class $Proxy is executed
- The sell method in the Proxy class $Proxy calls the invoke method of the subclass object of the InvocationHandler interface
- The invoke method executes the sell method in the class to which the real object belongs through reflection
CGLib dynamic proxy
CGLib is a powerful and high-performance code generation package. It provides agents for classes that do not implement interfaces and provides a good supplement to the dynamic agent of JDk
CGLib is a package provided by a third party, so you need to introduce the coordinates of the jar package
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>
Code example
//Train station ticket public class TrainStation{ public void sell() { System.out.println("Train station ticket"); } } //Proxy object factory, which is used to obtain proxy objects public class ProxyFactory implements MethodInterceptor { //Real object private TrainStation trainStation = new TrainStation(); public TrainStation getProxyObject(){ //Create an Enhancer object, similar to the Proxy class in JDK Proxy Enhancer enhancer = new Enhancer(); //Sets the bytecode object of the parent class enhancer.setSuperclass(TrainStation.class); //Set callback function enhancer.setCallback(this); //Create proxy object TrainStation proxyObject = (TrainStation)enhancer.create(); return proxyObject; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //Call the method of the target object System.out.println("Service fees are charged at the consignment point"); trainStation.sell(); return null; } } //test public class Client { public static void main(String[] args) { //Create proxy factory object ProxyFactory proxyFactory = new ProxyFactory(); //Get proxy object TrainStation proxyObject = proxyFactory.getProxyObject(); //Call the sell method in the proxy object to sell tickets proxyObject.sell(); } }
Comparison of three agents
JDK agent and CGLib agent
CGLib is used to implement dynamic proxy. The bottom layer of CGLib uses ASM bytecode generation framework and bytecode technology to generate proxy classes. The only thing to note is that CGLib cannot proxy classes or methods whose life is final, because CGLib principle is to dynamically generate subclasses of the proxy class
In jdk1 8 after gradually optimizing the JDK dynamic agent, the efficiency of JDK agent is higher than that of CGLib agent under the condition of less calls. Therefore, if there is an interface, use JDK dynamic proxy, and if there is no interface, use CGLib proxy
Dynamic agent and static agent
Compared with static proxy, the biggest advantage of dynamic proxy is that all the methods declared in the interface are transferred to a centralized method of the calling processor (InvocationHandler). In this way, when there are a large number of interface methods, we can handle them flexibly without transferring each method like a static agent
If a method is added to the interface, all proxy classes need to implement this method in addition to all implementation classes in the static proxy mode. It increases the complexity of code maintenance. Dynamic agents do not have such problems
Advantages of agent mode
- The proxy pattern acts as a mediator between the client and the target object and protects the target object
- The proxy object can extend the function of the target object
- The proxy mode can separate the client from the target object and reduce the coupling of the system to a certain extent
Disadvantages of agent mode
It increases the complexity of the system
Usage scenario
Remote agent
Local services request remote services over the network. In order to realize local to remote communication, we need to realize network communication and deal with possible exceptions. For good code design and maintainability, we hide the network communication part and expose only one interface to the local service. Through this interface, we can access the functions of the remote service without paying too much attention to the communication part code
Firewall agent
When you configure your browser to use the proxy function, the firewall will transfer your browser's request to the Internet; When the Internet returns a response, the proxy server forwards it to your browser
Protection agent
Control access to an object. If necessary, different users can be provided with different levels of permissions
Adapter mode
Many examples in life, such as chargers and card readers, can be abstracted into adapters
Convert the interface of a class into another interface that the customer wants, so that the classes that cannot work together due to incompatible interfaces can work together
The adapter mode is divided into class adapter mode and object adapter mode. The former has higher coupling between classes than the latter, and requires programmers to understand the internal structure of relevant components in the existing component library, so there are relatively few applications
structure
Target interface: the interface expected by the current system business, which can make the abstract class interface
Adapter class (Adapee): it is the component interface in the existing component library accessed and adapted
Adapter class: it is a converter that converts the adapter interface into the target interface by inheriting or referring to the adapter object, so that the client can access the adapter in the format of the target interface
Class adapter mode
Reader code example
//Adapter interface public interface TFCard { //Read data from TF Card public String readTF(); //Write data to TF Card public void writeTF(String string); } //Adapter class public class TFCardImpl implements TFCard{ @Override public String readTF() { String msg = "TF Card read msg:hello world"; return msg; } @Override public void writeTF(String string) { System.out.println("write : "+string); } } //Target interface public interface SDCard { //Read data from SD card public String readSD(); //Write data to SD card public void writeSD(String string); } //Target class public class SDCardImpl implements SDCard{ @Override public String readSD() { String msg = "SD Card read msg:hello world"; return null; } @Override public void writeSD(String string) { System.out.println("write : "+string); } } //Computer class public class Computer { //Read data from SD card public String readSD(SDCard sdCard){ if(sdCard==null){ throw new NullPointerException("Cannot be null"); } return sdCard.readSD(); } } //adapter class public class SDAdapter extends TFCardImpl implements SDCard{ @Override public String readSD() { System.out.println("adapter read tf card"); return readTF(); } @Override public void writeSD(String string) { System.out.println("adapter write tf card"); writeTF(string); } } //test public class Client { public static void main(String[] args) { //Create computer object Computer computer = new Computer(); //Read data from SD card String msg = computer.readSD(new SDAdapter()); System.out.println(msg); } }
The class adapter pattern violates the principle of composite reuse. The class adapter makes the client class available when there is an interface specification, otherwise it is not available
Object Adapter Pattern
Modify the adapter class in the above example to no longer inherit the adapter, but rely on the adapter
//adapter class public class SDAdapterTF implements SDCard{ //Declare adapter class private TFCard tfCard; public SDAdapterTF(TFCard tfCard) { this.tfCard = tfCard; } @Override public String readSD() { System.out.println("adapter read tf card"); return tfCard.readTF(); } @Override public void writeSD(String string) { System.out.println("adapter write tf card"); tfCard.writeTF(string); } }
Note: another adapter mode is the interface adapter mode. When you do not want to implement all the methods in an interface, you can create an abstract class Adpater to implement all the methods. At this time, we only need to inherit the abstract class
Application scenario
- The previously developed system has classes that meet the functional requirements of the new system, but their interfaces are inconsistent with those of the new system
- Use the components provided by the third party, but the component interface definition is different from the interface definition you need
JDK source code analysis
The adaptation of Reader (character stream) and InputStream (byte stream) uses InputStreamReader and OutputStreamWriter
InputStreamReader and OutputStreamWriter inherit from Java The Reader and writer in the IO package implement the abstract and unimplemented methods in them
By analyzing the source code, we can know that InputStreamReader does the conversion from InputStream byte stream class to Reader character stream. The design and implementation of the streetdecoder actually adopts the adapter pattern
Decorator mode
The mode of dynamically adding some responsibilities to the object without changing the existing object structure, that is, adding its additional functions
structure
Abstract build role (Component): define an abstract interface to standardize objects ready to receive additional responsibilities
Concrete Component: implement abstract components and add some responsibilities to them by decorating roles
Abstract Decorator: inherits or implements abstract components and contains instances of specific components. The functions of specific components can be extended through their subclasses
Concrete decorator: implement the relevant methods of abstract decoration and add additional responsibilities to the concrete construction object
Fast food restaurant code example
//Fast food class (Abstract build role) public abstract class FastFood { private float price;//Price private String desc;//describe public FastFood(float price, String desc) { this.price = price; this.desc = desc; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public abstract float cost(); } //Fried rice (specific component role) public class FriedRice extends FastFood{ public FriedRice() { super(10,"Fried rice"); } @Override public float cost() { return getPrice(); } } //Fried noodles (specific component role) public class FriedNoodles extends FastFood{ public FriedNoodles() { super(12,"Stir-Fried Noodles with Vegetables"); } @Override public float cost() { return getPrice(); } } //Decoration class (abstract decoration role) public abstract class Garnish extends FastFood{ public Garnish(FastFood fastFood,float price, String desc) { super(price, desc); this.fastFood=fastFood; } //Declare variables for fast food classes private FastFood fastFood; public FastFood getFastFood() { return fastFood; } public void setFastFood(FastFood fastFood) { this.fastFood = fastFood; } } //Eggs (specific decorators) public class Egg extends Garnish{ public Egg(FastFood fastFood) { super(fastFood, 1, "egg"); } @Override public float cost() { //Calculate price return getPrice()+getFastFood().cost(); } @Override public String getDesc() { return super.getDesc()+getFastFood().getDesc(); } } //Bacon (specific decorator) public class Bacon extends Garnish{ public Bacon(FastFood fastFood) { super(fastFood, 2, "Bacon"); } @Override public float cost() { //Calculate price return getPrice()+getFastFood().cost(); } @Override public String getDesc() { return super.getDesc()+getFastFood().getDesc(); } } public class Client { public static void main(String[] args) { //Order a fried rice FastFood food = new FriedRice(); //Add eggs food = new Egg(food); //Add eggs food = new Egg(food); System.out.println(food.getDesc()+" "+food.getPrice()); } }
advantage
- Decorator mode can bring more flexible extension functions than inheritance, and it is more convenient to use. It can obtain diversified results with different behavior states by combining different decorating objects. Decorator mode has better expansibility than inheritance, and perfectly follows the opening and closing principle. Inheritance is a static additional responsibility, while decoration is a dynamic additional responsibility
- Decorative classes and decorated classes can develop independently without coupling each other. Decorative pattern is an alternative pattern of inheritance. Decorative pattern can dynamically expand the functions of an implementation class
Usage scenario
- When the system cannot be extended by inheritance, or inheritance is not conducive to the expansion and maintenance of the system
- Add responsibilities to a single object in a dynamic and transparent manner without affecting other objects
- When the function requirements of the object can be added dynamically or revoked dynamically
JDK source code analysis
The wrapper class in the IO stream uses decorator mode. BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter
BufferedWriter uses decorator mode to enhance the implementation class of writer subclass and add buffer to improve the efficiency of writing data
Difference between agent and decorator
Differences between static proxy and decorator patterns
Similarities:
All of them should implement the same business interface as the target class
Declare the target object in both classes
Can enhance the target method without modifying the target class
difference:
The purpose is different. When decorating, it is to enhance the target object; In order to protect and hide the target object during static proxy
The construction place of the target object is different. The decorator is passed in from the outside and can be passed through the construction method; A static proxy is created inside the proxy to hide the target object
Bridging mode
Separate abstraction from implementation so that they can change independently. It is realized by replacing the inheritance relationship with the combination relationship, which reduces the coupling between the two variable dimensions of abstraction and implementation
structure
Abstraction: defines an abstract role and contains a reference to an implementation object
Refined Abstraction: it is a subclass of the abstract role, which implements the business methods in the parent class and calls the business methods in the abstract role through the composition relationship
Implementer: defines the interface to implement the role, and extends the abstract role calls
Concrete implementer: gives the concrete implementation of the implementation role interface
Video player code example
//Video files, role-based public interface VideoFile { //Decoding function void decode(String fileName); } //avi video file, specific role public class AVIFile implements VideoFile{ @Override public void decode(String fileName) { System.out.println("avi Video files:"+fileName); } } //rmvb video file, specific role public class RMVBFile implements VideoFile{ @Override public void decode(String fileName) { System.out.println("rmvb Video files:"+fileName); } } //Abstract operating system classes, abstract roles public abstract class OperatingSystem { //Declare the videoFile variable protected VideoFile videoFile; public OperatingSystem(VideoFile videoFile) { this.videoFile = videoFile; } public abstract void play(String fileName); } //Extended abstract role (Mac OS) public class Mac extends OperatingSystem { public Mac(VideoFile videoFile) { super(videoFile); } @Override public void play(String fileName) { videoFile.decode(fileName); } } //Extended abstract role (windows operating system) public class Windows extends OperatingSystem { public Windows(VideoFile videoFile) { super(videoFile); } @Override public void play(String fileName) { videoFile.decode(fileName); } } //test public class Client { public static void main(String[] args) { //Create mac system object OperatingSystem mac = new Mac(new AVIFile()); //Play video files using the operating system mac.play("War wolf"); } }
advantage
- The bridging mode improves the scalability of the system. If one of the two change dimensions is extended arbitrarily, there is no need to modify the original system. For example, if there is still a video file type wmv, we only need to define another interface that implements VideoFile, and other classes do not need to be changed
- Make details transparent to customers
Usage scenario
- When a class has two independently changing dimensions and both dimensions need to be extended
- When a system does not want to use inheritance or the number of system classes increases sharply due to multi-level inheritance
- When a system needs to add more flexibility between the abstract and concrete roles of components. Avoid establishing static inheritance relationship between the two levels. Through the bridge mode, they can establish an association relationship in the abstract level
Appearance mode
Also known as facade mode, it is a mode that can be accessed more easily by using multiple complex subsystems by providing a consistent interface. This mode has a unified interface to the outside, and the external application does not care about the specific details of the internal subsystem, which will greatly reduce the complexity of the application and provide the maintainability of the program.
Appearance pattern is a typical application of "Dimitri's law"
structure
- Facade: provides a common interface for multiple subsystems
- Subsystem role: it realizes some functions of the system, and customers can access it through appearance role
Code example
Intelligent appliance control
//Electric lamps public class Light { //turn on the light public void on(){ System.out.println("Turn on the light"); } //Turn off the lights public void off(){ System.out.println("Turn off the lights"); } } //Television public class TV { //turn on the light public void on(){ System.out.println("Turn on the TV"); } //Turn off the lights public void off(){ System.out.println("Turn off the TV"); } } //Air conditioning public class AirCondition { //turn on the light public void on(){ System.out.println("Turn on the air conditioner"); } //Turn off the lights public void off(){ System.out.println("Turn off the air conditioner"); } } //Appearance class public class SmartAppliancesFacade { //Aggregate electric lamp object, TV object and air conditioning object private Light light; private TV tv; private AirCondition airCondition; public SmartAppliancesFacade() { light = new Light(); tv = new TV(); airCondition = new AirCondition(); } //Voice control public void say(String messsage){ if (messsage.contains("open")){ on(); } else if(messsage.contains("close")){ off(); } else { System.out.println("I don't understand"); } } //One piece open private void on(){ light.on(); tv.on(); airCondition.on(); } //One off private void off(){ light.off(); tv.off(); airCondition.off(); } } //test public class Client { public static void main(String[] args) { //Creating smart audio objects SmartAppliancesFacade facade = new SmartAppliancesFacade(); //Control appliances facade.say("Turn on the appliance"); facade.say("Turn off appliances"); } }
advantage
- The coupling between the subsystem and the client is reduced, so that the change of the subsystem will not affect the calling of his client class
- It shields the subsystem components from customers, reduces the number of objects handled by customers, and makes the subsystem easier to use
shortcoming
It does not comply with the opening and closing principle, and the modification is very troublesome
Usage scenario
- When building a hierarchical system, defining the entry point of each layer in the subsystem using appearance patterns can simplify the dependencies between subsystems
- When there are many complex subsystems, the appearance pattern can design a simple interface for the system to access the outside world
- When there is a great connection between the client and multiple subsystems, the hot topic appearance pattern can separate them, so as to provide the independence and portability of the subsystem
Combination mode
Also known as partial whole pattern, it is used to treat a group of similar objects as a single object. The combination mode combines objects according to the tree structure, which is used to represent the partial and overall levels. This type of design pattern is a structural pattern, which creates a tree structure of object groups
structure
Abstract root node (Component): defines the common methods and attributes of objects at all levels of the system, and some default behaviors and attributes can be defined in advance
Composite: define the behavior of branch nodes, store child nodes, and combine branch nodes and leaf nodes to form a tree structure
Leaf node: leaf node object, under which there are no branches, is the smallest unit of system level traversal
case
Software menu
//The menu component (Abstract root node) should inherit this class whether it is a menu or a menu item public abstract class MenuComponent { //Name of menu component, level protected String name; protected int level; //Add submenu public void add(MenuComponent menuComponent){ throw new UnsupportedOperationException(); } //Delete submenu public void remove(MenuComponent menuComponent){ throw new UnsupportedOperationException(); } //Gets the specified submenu public MenuComponent getChild(int index){ throw new UnsupportedOperationException(); } //Gets the name of the menu or menu item public String getName(MenuComponent menuComponent){ return name; } //Method for printing menu names (including submenus and submenu items) public abstract String print(); } //Menu item class: leaf node public class MenuItem extends MenuComponent{ public MenuItem(String name,int level) { this.name = name; this.level=level; } @Override public String print() { //Print the name of the menu item for (int i =0;i<level;i++){ System.out.println("--"); } System.out.println(name); return null; } } //Menu class: belongs to branch node public class Menu extends MenuComponent{ // A menu can have multiple submenus or submenu items private List<MenuComponent> menuComponentList = new ArrayList<>(); public Menu(String name,int level) { this.name = name; this.level=level; } @Override public void add(MenuComponent menuComponent){ menuComponentList.add(menuComponent); } @Override public void remove(MenuComponent menuComponent){ menuComponentList.remove(menuComponent); } @Override public MenuComponent getChild(int index){ return menuComponentList.get(index); } @Override public String getName(MenuComponent menuComponent){ return name; } @Override public String print() { //Print menu name for (int i =0;i<level;i++){ System.out.println("--"); } System.out.println(name); //Print the name of the submenu or submenu item for (MenuComponent component : menuComponentList) { component.print(); } return null; } } //test public class Client { public static void main(String[] args) { //Create menu tree MenuComponent menu1 = new Menu("Menu management",2); menu1.add(new MenuItem("Page access",3)); menu1.add(new MenuItem("Show access",3)); menu1.add(new MenuItem("Edit access",3)); menu1.add(new MenuItem("Delete access",3)); menu1.add(new MenuItem("New access",3)); MenuComponent menu2 = new Menu("Authority management",2); menu2.add(new MenuItem("Page access",3)); menu2.add(new MenuItem("Submit save",3)); MenuComponent menu3 = new Menu("Role management",2); menu3.add(new MenuItem("Page access",3)); menu3.add(new MenuItem("New role",3)); menu3.add(new MenuItem("Modify role",3)); //Create first level menu MenuComponent menu = new Menu("system management",1); menu.add(menu1); menu.add(menu2); menu.add(menu3); //Print menu name menu.print(); } }
Classification and advantages of combination patterns
When using composite mode, according to the definition form of abstract component class, we can divide the composite mode into transparent composite mode and safe composite mode
advantage
- Composite mode can clearly define hierarchical complex objects and represent all or part of the hierarchy of objects. It allows the client to ignore the differences of hierarchy and facilitate the control of the whole hierarchy
- The client can consistently use a composite structure or a single object in it, regardless of whether the single object or the whole composite structure is processed, which simplifies the client code
- It is convenient to add new branch nodes and leaf nodes in the combination mode without any modification to the existing class library, which conforms to the "opening and closing principle"
- The combination pattern provides a flexible solution for the object-oriented implementation of tree structure. Through the recursive combination of leaf nodes and branch nodes, complex tree structure can be formed, but the control of tree structure is very simple
Usage scenario
The combination mode is born in response to the tree structure, so the use scenario of the combination mode is where the tree structure appears. For example, file directory display, multi-level directory presentation and other tree structure data operations
Sharing element mode
Sharing technology is used to effectively support the reuse of a large number of fine-grained objects. By sharing existing objects, it can greatly reduce the number of objects to be created and avoid the overhead of a large number of similar objects, so as to improve the utilization of system resources
structure
There are two statuses in the meta mode:
Internal state, a shareable part that will not change with the change of environment
External state refers to the unshareable part that changes with the environment. The key to the implementation of shared meta mode is to distinguish the two states in the application and externalize the external state
Abstract meta role (Flyweight):
Usually, it is an interface or abstract class, which declares the public methods of the specific meta class in the abstract meta class. These methods can provide the internal data (internal state) of the meta object to the outside world, and can also set the external data (external state) through these methods
Concrete Flyweight:
It implements an abstract meta class, called meta object; Storage space is provided for internal states in specific meta classes. Usually, we can design specific meta classes in combination with the singleton pattern to provide unique meta objects for each specific meta class
Unsharable Flyweight:
Not all subclasses of the desired Abstract shared metaclass need to be shared. Subclasses that cannot be shared can be designed as non shared concrete shared metaclasses; When you need an object that does not share a specific meta class, you can create it directly by instantiation
Flyweight Factory role:
Responsible for creating and managing meta roles. When a customer object requests a meta object, the meta factory checks whether there are qualified meta objects in the system, and if so, provides them to the customer; If no materialization exists, a new meta object is created
Example
Tetris
//Abstract meta role public abstract class AbstractBox { //Method of obtaining graphics public abstract String getShape(); //Display graphics and colors public void display(String color){ System.out.println("shape:"+getShape()+" color:"+color); } } //I graphics class, specific meta roles public class IBox extends AbstractBox{ @Override public String getShape() { return "I"; } } //L graphics class, specific meta role public class LBox extends AbstractBox{ @Override public String getShape() { return "L"; } } //O graphic class, specific meta role public class OBox extends AbstractBox{ @Override public String getShape() { return "O"; } } //Factory class, which is designed as a single example public class BoxFactory { private HashMap<String,AbstractBox> map; //Initialize in the constructor private BoxFactory() { map = new HashMap<>(); map.put("I",new IBox()); map.put("L",new LBox()); map.put("O",new OBox()); } //Get drawing objects by name public AbstractBox getShape(String name){ return map.get(name); } //Provide a method to get the factory class object private static BoxFactory boxFactory = new BoxFactory(); public static BoxFactory getInstance(){ return boxFactory; } } //test public class Client { public static void main(String[] args) { //Get I drawing objects AbstractBox i = BoxFactory.getInstance().getShape("I"); i.display("grey"); //Get O drawing objects AbstractBox o = BoxFactory.getInstance().getShape("O"); o.display("gules"); //Get O drawing objects AbstractBox o1 = BoxFactory.getInstance().getShape("O"); o1.display("black"); System.out.println(o==o1); } }
Advantages, disadvantages and usage scenarios
advantage
- Greatly reduce the number of similar or identical objects in memory, save system resources and provide system performance
- The external state in the sharing mode is relatively independent and does not affect the internal state
shortcoming
In order to make objects can be shared, it is necessary to externalize some states of shared meta objects, separate internal states from external states, and make program logic complex
Usage scenario
- A system has a large number of the same or similar objects, resulting in a large amount of memory consumption
- Most of the state of an object can be externalized, and these externalizations can be passed into the object
- When using the sharing mode, you need to maintain a sharing pool for storing sharing objects, which requires certain system resources. Therefore, it is worth using the sharing mode when you need to reuse the sharing objects for many times
JDK source code analysis
The Integer class uses meta mode
Behavioral patterns (11)
Behavioral pattern is used to describe the complex process control of a program at run time, that is, how multiple classes or objects cooperate with each other to complete tasks that can not be completed by a single object. It involves the allocation of responsibilities between algorithms and objects
Behavioral patterns are divided into class behavior patterns and object behavior patterns. The former uses inheritance mechanism to allocate behavior between classes, and the latter uses combination or aggregation to allocate behavior between objects. Because the coupling degree of composite relationship or aggregation relationship is lower than that of inheritance relationship and meets the "composite Reuse Principle", the object behavior pattern has more flexibility than the class behavior pattern
Behavioral patterns are divided into:
- Template method pattern
- Strategy mode
- Command mode
- Responsibility chain model
- State mode
- Observer mode
- Intermediary model
- Iterator mode
- Visitor mode
- Memo mode
- Interpreter mode
Template method pattern
Define the algorithm skeleton in an operation, and delay some steps of the algorithm to the subclass, so that the subclass can redefine some specific steps of the algorithm without changing the structure of the algorithm
structure
Abstract class( Abstract Class): Responsible for giving the outline and skeleton of an algorithm. It consists of a template method and several basic methods Template method: defines the skeleton of the algorithm and calls its basic methods in a certain order Basic method: it is the method to realize each step of the algorithm and an integral part of the template method. The basic methods can be divided into three types: Abstract method( Abstract Method): An abstract method is declared by an abstract class and implemented by its concrete subclasses Specific method( Concrete Method): A concrete method is declared and implemented by an abstract class or concrete class, and its subclasses can be overridden or inherited directly Hook method( Hook Method): It has been implemented in abstract classes, including logical methods for judgment and empty methods that need subclass rewriting Specific subclass( Concrete Class): Implement the abstract methods and hook methods defined in the abstract class, which are the constituent steps of a top-level logic
Case realization
Stir fry
//Template method, abstract class, defining template method and basic method public abstract class AbstractClass { //Template method definition public final void cookProcess(){ pourOil(); heatOil(); pourVegetable(); pourSauce(); fry(); } public void pourOil(){ System.out.println("Pour oil"); } public void heatOil(){ System.out.println("hot oil"); } //Put in the ingredients public abstract void pourVegetable(); //Put in the seasoning public abstract void pourSauce(); public void fry(){ System.out.println("Stir fry"); } } //Subclass public class ConcreteClass_BaoCai extends AbstractClass{ @Override public void pourVegetable() { System.out.println("Put meat"); } @Override public void pourSauce() { System.out.println("Put chili"); } } //Subclass public class ConcreteClass_CaiXin extends AbstractClass{ @Override public void pourVegetable() { System.out.println("Put green vegetables"); } @Override public void pourSauce() { System.out.println("Put salt"); } } //test public class Client { public static void main(String[] args) { ConcreteClass_BaoCai c1 = new ConcreteClass_BaoCai(); c1.cookProcess(); ConcreteClass_CaiXin c2 = new ConcreteClass_CaiXin(); c2.cookProcess(); } }
Advantages and disadvantages
advantage
- Improve code reusability, put the same part of the code in the abstract parent class, and put different code in different subclasses
- It realizes the reverse control, calls the operation of its subclasses through a parent class, extends different behaviors through the specific implementation of subclasses, and realizes the reverse control, which conforms to the 'opening and closing principle'
shortcoming
- A subclass needs to be defined for each different implementation, which leads to an increase in the number of classes, a larger system and a more abstract design
- Abstract methods in the parent class are implemented by subclasses, and the execution results of subclasses will affect the results of the parent class, which leads to a reverse control structure, which improves the difficulty of code reading
Applicable scenario
- The overall steps of the algorithm are very fixed, but when individual parts are changeable, you can use the template method pattern to abstract the easily changeable parts for subclass implementation
- It is necessary to determine the execution time of a step in the parent algorithm through the subclass, so as to realize the reverse control of the subclass over the parent
JDK source code analysis
The InputStream class uses the template method pattern. The abstract read method is defined in the InputStream class and called
Strategy mode
This pattern defines a number of column algorithms and encapsulates each algorithm so that they can be replaced with each other, and the change of the algorithm will not affect the customers using the algorithm. Policy pattern tree object behavior pattern, which separates the responsibility of using the algorithm from the implementation of the algorithm by encapsulating the algorithm, and delegates it to different objects to manage these algorithms
structure
Abstract Strategy: This is an abstract role, usually implemented by an interface or abstract class. This role gives the interfaces required for all specific policy classes
Concrete Strategy: it implements the interface of abstract policy definition and provides specific algorithm implementation or behavior
Context: holds a reference to a policy class, which is finally called to the client
case
sales promotion
//Abstract policy class public interface Strategy { public void show(); } //Specific policy class, encapsulation algorithm public class StrategyA implements Strategy{ @Override public void show() { System.out.println("20 minus 100"); } } //Specific policy class, encapsulation algorithm public class StrategyB implements Strategy{ @Override public void show() { System.out.println("10% off"); } } //Specific policy class, encapsulation algorithm public class StrategyC implements Strategy{ @Override public void show() { System.out.println("50 vouchers for 200"); } } //Environment, promoter, aggregation strategy public class SalesMan { //Aggregation strategy private Strategy strategy; public SalesMan(Strategy strategy) { this.strategy = strategy; } //The content of the event is displayed by the promoter public void salesManShow(){ strategy.show(); } } //test public class Client { public static void main(String[] args) { //Spring festival activities SalesMan salesMan1 = new SalesMan(new StrategyA()); salesMan1.salesManShow(); //Valentine's Day activities SalesMan salesMan2 = new SalesMan(new StrategyB()); salesMan2.salesManShow(); //Store celebration activities SalesMan salesMan3 = new SalesMan(new StrategyC()); salesMan3.salesManShow(); } }
Advantages and disadvantages
advantage
- Policy classes can be switched freely. Since policy classes all implement the same interface, they can be switched freely
- It is easy to expand. To add a new policy class, you only need to add a specific policy class. There is basically no need to change the original code, which conforms to the "opening and closing principle"
- Avoid using multiple conditional selection statements (if else) and fully reflect the idea of object-oriented design
shortcoming
- The client must know all policy classes and decide which policy class to use
- The policy pattern will result in many policy classes. You can reduce the number of objects to a certain extent by using the meta pattern
Usage scenario
- When a system needs to dynamically select one of the centralized algorithms, each algorithm can be encapsulated into a policy class
- A class defines a variety of behaviors, and these behaviors appear in the form of multiple conditional statements in the operation of this class. Conditional branches can be moved into their respective policy classes to replace these conditional statements
- Each algorithm in the system is completely independent of each other, and it is required to hide the implementation details of the specific algorithm from the customer
- When the system requires that the customer using the algorithm should not know the data it operates, the policy pattern can be used to hide the data structure related to the algorithm
JDK source code analysis
The policy pattern in Comparator has a sort method in the Arrays class
Command mode
Encapsulating a request as an object separates the responsibility of sending the request from the responsibility of executing the request. In this way, the two communicate through the command object, which is convenient to store, transfer, call, add and manage the command object
structure
Abstract Command class role (Command): defines the interface of the Command and declares the method to execute
Concrete Command: a specific command that implements the command interface; Usually, the receiver will be held and the receiver's function will be called to complete the operation to be performed by the command
Implementer, Receiver role: Receiver, the object that actually executes the command. Any class can become a Receiver as long as it can implement the corresponding functions required by the command
Caller and requester roles (Invoker): command objects are required to execute requests. They usually hold command objects, and many command objects can be held. This is similar to the place where the client actually triggers the command and requires the command to perform the corresponding operation, that is, it is equivalent to using the entry of the command object
Sample code
Ordering process
//Abstract command class public interface Command { void execute(); } //Specific command class public class OrderCommand implements Command{ //Holding recipient object private SeniorChef seniorChef; private Order order; public OrderCommand(SeniorChef seniorChef, Order order) { this.seniorChef = seniorChef; this.order = order; } @Override public void execute() { System.out.println(order.getDiningTable()); order.getFoodDir().entrySet().forEach(x -> seniorChef.makeFood(x.getKey(),x.getValue()) ); System.out.println("finish"); } } //Order class, public class Order { //Table number private int diningTable; //Meals and scores private Map<String,Integer> foodDir = new HashMap<>(); public int getDiningTable() { return diningTable; } public void setDiningTable(int diningTable) { this.diningTable = diningTable; } public Map<String, Integer> getFoodDir() { return foodDir; } public void setFoodDir(String name, int num) { foodDir.put(name,num); } } //Chef class public class SeniorChef { public void makeFood(String name,int num){ System.out.println(name +" "+num); } } //The waiter class belongs to the caller role public class Waitor { //Hold multiple command objects private List<Command> commands = new ArrayList<>(); public void setCommand(Command cmd){ commands.add(cmd); } //Function of initiating command public void orderUp(){ System.out.println("Here comes the order"); for (Command command : commands) { if(command!=null){ command.execute(); } } } } //test public class Client { public static void main(String[] args) { //Create order object Order order1 = new Order(); order1.setDiningTable(1); order1.setFoodDir("Potato silk",1); order1.setFoodDir("rice",1); Order order2 = new Order(); order2.setDiningTable(1); order2.setFoodDir("Dried tofu in old soup",1); order2.setFoodDir("rice",1); //Create chef object SeniorChef seniorChef = new SeniorChef(); //Create command object OrderCommand cmd1 = new OrderCommand(seniorChef,order1); OrderCommand cmd2 = new OrderCommand(seniorChef,order2); //Create attendant Waitor waitor = new Waitor(); waitor.setCommand(cmd1); waitor.setCommand(cmd2); //The waiter initiates an order waitor.orderUp(); } }
Advantages and disadvantages
advantage
- Reduce the coupling of the system. Command mode can decouple the object that invokes the operation from the object that implements the operation
- Adding or deleting commands is very convenient. Using the command mode, adding and deleting commands will not affect other classes. It meets the opening and closing principle and is flexible for expansion
- Macro commands can be implemented. Command mode can be combined with combined mode to assemble multiple commands into a combined command, that is, macro command
- It is convenient to realize Undo and Redo operations. The command mode can be combined with the memo mode described later to realize the revocation and recovery of commands
shortcoming
- Using command mode may cause some systems to have too many specific command classes
- The system structure is more complex
Usage scenario
- The system needs to decouple the request caller and the request receiver, so that the caller and the receiver do not interact directly
- System requirements specify requests, queue requests, and execute requests at different times
- The system needs to support Undo and Redo operations of commands
JDK source code analysis
Runnable is a typical command mode. Runnable plays the role of command, Thread acts as the caller, and start method is the execution method
Responsibility chain model
Also known as the responsibility chain mode, in order to avoid coupling the request sender with multiple request handlers, the standing of all requests are connected into a chain by the migration object remembering the reference of the next object. When a request occurs, the request can be passed along the chain until an object handles it
structure
Abstract Handler: defines an interface for processing requests, including abstract processing methods and a subsequent connection
Concrete Handler: implement the processing method of the abstract handler to judge whether the request can be processed. If the request can be processed, it will be processed. If not, the request will be transferred to its successor
client: create a processing chain and submit a request to the specific handler object of the chain head. It doesn't care about the processing details and the transmission process of the request
Sample code
leave
//written request for leave public class LeaveRequest { private String name; private int num; private String context; public LeaveRequest(String name, int num, String context) { this.name = name; this.num = num; this.context = context; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } public String getContext() { return context; } public void setContext(String context) { this.context = context; } } //group leader public class GroupLeader extends Handler { public GroupLeader() { super(0,Handler.num_one); } @Override protected void handlerLeave(LeaveRequest leave) { System.out.println(leave.getName()+" leave"+leave.getNum()+"Day reason:"+leave.getContext()); System.out.println("The team leader agrees"); } } //division manager public class Manager extends Handler { public Manager() { super(Handler.num_one,Handler.num_three); } @Override protected void handlerLeave(LeaveRequest leave) { System.out.println(leave.getName()+" leave"+leave.getNum()+"Day reason:"+leave.getContext()); System.out.println("The Department Manager agrees"); } } //general manager public class GeneralManager extends Handler { public GeneralManager() { super(Handler.num_three,Handler.num_seven); } @Override protected void handlerLeave(LeaveRequest leave) { System.out.println(leave.getName()+" leave"+leave.getNum()+"Day reason:"+leave.getContext()); System.out.println("The general manager agrees"); } } //Abstract handler class public abstract class Handler { protected final static int num_one = 1; protected final static int num_three = 3; protected final static int num_seven = 7; //Leave days handled by the leader private int numStart; private int numEnd; //Declare successor (superior leader) private Handler nextHandler; public void setNextHandler(Handler nextHandler) { this.nextHandler = nextHandler; } public Handler(int numStart) { this.numStart=numStart; } public Handler(int numStart, int numEnd) { this.numStart = numStart; this.numEnd = numEnd; } //Methods for leaders at all levels to deal with leave requests protected abstract void handlerLeave(LeaveRequest leave); //Submit leave slip public final void submit(LeaveRequest leave){ //Approved by the leader this.handlerLeave(leave); if(this.nextHandler != null && leave.getNum()>this.numEnd){ //Submit to superior this.nextHandler.submit(leave); }else{ System.out.println("End of process"); } } } //test public class Client { public static void main(String[] args) { //Create a leave slip object LeaveRequest leaveRequest = new LeaveRequest("Xiao Ming", 1, "ill"); //Create leaders at all levels GroupLeader groupLeader = new GroupLeader(); Manager manager = new Manager(); GeneralManager generalManager = new GeneralManager(); //Set processing chain groupLeader.setNextHandler(manager); manager.setNextHandler(generalManager); //Xiao Ming submits an application for leave groupLeader.submit(leaveRequest); } }
Advantages and disadvantages
advantage
- This mode reduces the coupling between the sender and receiver of the request
- The scalability of the system is enhanced. New request processing classes can be added as needed to meet the opening and closing principle
- It increases the flexibility of assigning responsibilities to objects. When the workflow changes, you can dynamically change the members in the chain or modify their order, or dynamically add or delete responsibilities
- The responsibility chain simplifies the connection between objects. An object only needs to maintain a reference to its successor without maintaining the references of all other processors, avoiding the use of many if or if... else statement
- Responsibility sharing: each class only needs to deal with its own work, and those that cannot be handled are transferred to the next object for completion. The scope of responsibility of each class is clarified, which conforms to the principle of single responsibility of the class
shortcoming
- There is no guarantee that every request will be processed. Since a request does not have a clear receiver, it cannot be guaranteed that it will be processed. The request may reach the end of the chain and cannot be processed
- For a long responsibility chain, the processing of requests may involve multiple processing objects, which will have a certain impact on the system performance
- The rationality of establishing the responsibility chain depends on the client, which increases the complexity of the client and may lead to system errors due to the error of the responsibility chain, such as circular call
Source code analysis
In Java Web, FilterChain is a typical application of the responsibility chain (filter) pattern
State mode
For stateful objects, complex 'judgment logic' is extracted into different state objects, allowing state objects to change their behavior when their internal state changes
structure
Context: also known as context, it defines the interface required by the client program, maintains a current state, and delegates state related operations to the current state object for processing
Abstract state role: defines an interface to encapsulate the behavior corresponding to a specific state in an environment object
Context State: implements the behavior corresponding to the abstract state
Code example
//Abstract state class public abstract class LiftState { //Declare environment role class objects public Context context; public void setContext(Context context) { this.context = context; } public abstract void open(); public abstract void close(); public abstract void run(); public abstract void stop(); } //Environment role class public class Context { //Defines constants for the corresponding state object public final static OpenningState opening_state = new OpenningState(); public final static OpenningState closing_state = new OpenningState(); public final static OpenningState running_state = new OpenningState(); public final static OpenningState stopping_state = new OpenningState(); //Defines a current elevator state variable private LiftState liftState; public LiftState getLiftState() { return liftState; } public void setLiftState(LiftState liftState) { this.liftState = liftState; this.liftState.setContext(this); } public void open(){ this.liftState.open(); } public void close(){ this.liftState.close(); } public void run(){ this.liftState.run(); } public void stop(){ this.liftState.stop(); } } //Elevator opening status class public class OpenningState extends LiftState{ @Override public void open() { System.out.println("open"); } @Override public void close() { //Set status super.context.setLiftState(Context.closing_state); //Call the close method in the context in the current state super.context.close(); } @Override public void run() { //Don't do anything? } @Override public void stop() { //Don't do anything? } } //test public class Client { public static void main(String[] args) { Context context = new Context(); context.setLiftState(new RunningState()); context.open(); context.close(); context.run(); context.stop(); } }
Advantages and disadvantages
advantage
- Put all the behaviors related to a state into one class, and you can easily add new states. You can change the behavior of the object by changing the state of the object
- Allow state transition logic and what is the issue of state objects, rather than a huge conditional statement block
shortcoming
- The use of state mode will inevitably increase the number of system classes and objects
- The structure and implementation of state pattern are complex. If it is not used properly, it will lead to the confusion of program structure and code
- The state mode does not support the opening and closing principle very well
Usage scenario
- When an object's behavior depends on its state and it must change its behavior according to its state at run time, you can consider using state mode
- An operation contains a huge branch structure, and these branches depend on the state of the object
Observer mode
Also known as publish subscribe mode, it defines a one to many dependency, allowing multiple observer objects to listen to a topic object at the same time. When the state of the subject object changes, it will notify all observer objects that they can update themselves automatically
structure
Subject: abstract topic (Abstract observer). The abstract topic role saves all observer objects in a collection. Each topic can be used by any number of observers. The abstract topic provides an interface to add and delete observer objects
ConcreteSubject: a specific subject (specific observer). This role stores the relevant status in the specific observer object. When the internal status of a specific subject changes, it sends a notice to all registered observers
Observer: an abstract observer, which defines an update interface to update itself when notified of a topic change
ConcreteObserver: a concrete observer, which implements the update interface defined by the abstract observer, so as to update its own status when notified of the subject change
Code example
WeChat official account
//Abstract observer public interface Observer { void update(String message); } //Specific observer public class WeiXinUser implements Observer{ private String name; public WeiXinUser(String name) { this.name = name; } @Override public void update(String message) { System.out.println(name+"-"+message); } } //Abstract theme public interface Subject { //Add subscriber (add observer) void attach(Observer observer); //Delete subscriber void detach(Observer observer); //Notify subscribers of updates void notify(String message); } //Specific theme public class SubscriptionSubject implements Subject{ //Define a collection to hold multiple observers private List<Observer> weixinUserList = new ArrayList<>(); @Override public void attach(Observer observer) { weixinUserList.add(observer); } @Override public void detach(Observer observer) { weixinUserList.remove(observer); } @Override public void notify(String message) { //Traversal set for (Observer observer : weixinUserList) { observer.update(message); } } } public class Client { public static void main(String[] args) { //Create official account number SubscriptionSubject subject = new SubscriptionSubject(); //Subscribing to official account subject.attach(new WeiXinUser("Arilia")); subject.attach(new WeiXinUser("an introduction to")); subject.attach(new WeiXinUser("Cherish")); subject.attach(new WeiXinUser("EZ")); //The official account is updated. subject.notify("Go to the gym tomorrow"); } }
Advantages and disadvantages
advantage
- It reduces the coupling relationship between the target and the observer, which is an abstract coupling relationship
- The observed sends a notification, and all registered observers will receive the information
shortcoming
- If there are many observers, it will take time for all observers to receive the notification sent by the observed
- If the observer has circular dependency, the notification sent by the observer will make the observer call circularly, which will lead to system crash
Usage scenario
- There is a one to many relationship between objects. Changes in the state of one object will affect other objects
- When an abstract model has two aspects, one of which depends on the other
Implementation provided in JDK
In Java, observer patterns are defined through the Observer class (topic) and observer interface (observer). As long as their subclasses are implemented, observer pattern instances can be written
Intermediary model
It is also called mediation mode. It defines an intermediary role to encapsulate the interaction between a series of objects, so that the coupling between the original objects is loose, and the interaction between them can be changed independently
structure
Abstract Mediator role: it is the interface of the Mediator and provides the abstract method for the colleague object to register and forward the colleague object information
Concrete mediator: implement the mediator interface, define a List to manage colleague objects and coordinate the interaction between various colleague roles. Therefore, it depends on colleague roles
Abstract Colleague role: define the interface of Colleague class, save the mediator object, provide the abstract method of Colleague object interaction, and realize the public functions of all Colleague classes that sell to each other
Concrete Colleague role: it is the implementer of the abstract colleague class. When it is necessary to interact with other colleague objects, the mediator object is responsible for the subsequent interaction
Sample code
rent an apartment
//Abstract mediator class public abstract class Mediator { //Communication methods public abstract void constact(String message,Person person); } //Specific intermediary public class MediatorStructure extends Mediator{ //Aggregate 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; } @Override public void constact(String message, Person person) { if (person == houseOwner){ tenant.getMessage(message); }else{ houseOwner.getMessage(message); } } } //Abstract colleague class public abstract class Person { protected String name; protected Mediator mediator; public Person(String name, Mediator mediator) { this.name = name; this.mediator = mediator; } } //Specific colleague role classes public class Tenant extends Person{ public Tenant(String name, Mediator mediator) { super(name, mediator); } //Methods of communicating with intermediaries public void constact(String message){ mediator.constact(message,this); } //Methods of obtaining information public void getMessage(String message){ System.out.println("rent an apartment "+name+" information"+message); } } //Specific colleague role classes public class HouseOwner extends Person{ public HouseOwner(String name, Mediator mediator) { super(name, mediator); } //Methods of communicating with intermediaries public void constact(String message){ mediator.constact(message,this); } //Methods of obtaining information public void getMessage(String message){ System.out.println("Homeowner "+name+" information"+message); } } //test public class Client { public static void main(String[] args) { //Create mediator object MediatorStructure mediator = new MediatorStructure(); //Create tenant object Tenant tenant = new Tenant("lisi", mediator); //Create homeowner object HouseOwner houseOwner = new HouseOwner("zhangsan", mediator); //Intermediaries should know the specific homeowners and renters mediator.setTenant(tenant); mediator.setHouseOwner(houseOwner); tenant.constact("I want to rent a house"); houseOwner.constact("I have a house here"); } }
Advantages and disadvantages
advantage
loosely coupled
The mediator model encapsulates the interaction between multiple colleague objects into the mediator object, so that the colleague objects are loosely coupled and can basically achieve complementary dependence. In this way, colleague objects can be changed and reused independently, instead of affecting the whole body as before
Centralized control interaction
The interaction of multiple colleague objects is encapsulated in the mediator object for centralized management, so that when these interaction behaviors change, only the mediator object needs to be modified. Of course, if the system is ready, the mediator object needs to be extended, and each peer class does not need to be modified
One to many association is transformed into one to one association
When the mediator mode is not used, the relationship between colleague objects is usually one to many. After the mediator object is introduced, the relationship between mediator object and colleague object usually becomes two-way one-to-one, which will make the relationship between objects easier to understand and implement
shortcoming
When there are too many colleagues, the intermediary's responsibilities will be great, and he will become complex and huge, so that the system is difficult to maintain
Usage scenario
- There is a load reference relationship between objects in the system, and the system structure is chaotic and difficult to understand
- When you create an object that runs between multiple classes and do not want to generate new subclasses
Iterator mode
Provides an object to sequentially access a series of data in an aggregate object without exposing the internal representation of the aggregate object
structure
Abstract aggregate role: defines the interface for storing, adding and deleting aggregate elements and creating iterator objects at the first level
Concrete aggregate: implements an abstract aggregate class and returns an instance of a concrete iterator
Abstract Iterator role: defines the interface for accessing and traversing aggregation elements, usually including hasNext, next and other methods
Concrete iterator role: implement the method defined in the abstract iterator interface, complete the traversal of the aggregate object, and record the current location of the traversal
Code example
Define a container object that can store student objects, and hand over the function of traversing the container to the iterator
//Abstract iterator role public interface StudentIterator { //There are elements when judging boolean hasNext(); //Get next element Student next(); } //Concrete Iterator public class StudentIteratorImpl implements StudentIterator{ private List<Student> list; private int index=0; public StudentIteratorImpl(List<Student> list) { this.list = list; } @Override public boolean hasNext() { return index<list.size(); } @Override public Student next() { Student student = list.get(index); index++; return student; } } //Abstract aggregate role public interface StudentAggregate { //Add student function void addStudent(Student student); //Delete student function void removeStudent(Student student); //Get iterator object function StudentIterator getStudentIterator(); } //Specific aggregate roles public class StudentAggregateImpl implements StudentAggregate{ private List<Student> list = new ArrayList<>(); @Override public void addStudent(Student student) { list.add(student); } @Override public void removeStudent(Student student) { list.remove(student); } @Override public StudentIterator getStudentIterator() { return new StudentIteratorImpl(list); } } //element public class Student { private String name; private String number; public Student() { } public Student(String name, String number) { this.name = name; this.number = number; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", number='" + number + '\'' + '}'; } } public class Client { public static void main(String[] args) { //Create aggregate object StudentAggregateImpl studentAggregate = new StudentAggregateImpl(); //Add element studentAggregate.addStudent(new Student("zhangsan","111")); studentAggregate.addStudent(new Student("lisi","222")); studentAggregate.addStudent(new Student("wangwu","333")); studentAggregate.addStudent(new Student("zhangliu","444")); //Traversing aggregate objects //Get iterator object StudentIterator studentIterator = studentAggregate.getStudentIterator(); while (studentIterator.hasNext()){ Student next = studentIterator.next(); System.out.println(next.toString()); } } }
Advantages and disadvantages
advantage
- It supports traversing an aggregate object in different ways, and multiple traversal methods can be defined on the same aggregate object. In the iterator mode, we only need to replace the original iterator with a different iterator to change the traversal algorithm. We can also define the subclass of the iterator to support the new traversal method
- Iterators simplify aggregate classes. Due to the introduction of iterator, there is no need to provide data traversal and other methods in the original aggregation object, which can simplify the design of aggregation class
- In the iterator mode, it is convenient to add new aggregation classes and iterator classes due to the introduction of the abstraction layer, without modifying the original code, and meet the requirements of the opening and closing principle
shortcoming
The number of classes is increased, which increases the complexity of the system to a certain extent
Usage scenario
- When you need to provide multiple traversal methods for aggregate objects
- When it is necessary to provide a unified interface for traversing different aggregation structures
- When accessing the contents of an aggregate object without exposing the representation of its internal details
Visitor mode
Encapsulates some operations that act on the elements of a data structure. It can define the operations that act on these elements without changing the data structure
structure
Abstract Visitor role:
It defines the behavior of accessing each Element. Its parameters are the accessible elements. Theoretically, the number of methods is the same as the number of elements. It is not difficult to see that the visitor pattern requires that the number of Element classes cannot be changed
Concrete visitor role:
Give the specific behavior generated when accessing each element class
Abstract Element role:
Defines a method to receive visitors, which means that every element can be accessed by visitors
Concrete element role:
Provide a specific implementation of the receiving access method, which usually uses the method provided by the visitor to access the element class
Object Structure role:
The object structure mentioned in the definition is an abstract expression. Specifically, it can be understood as a class with container nature or object characteristics. It will contain a group of elements and can iterate these elements for visitors to visit
Code example
Feeding pets
Visitor role: the person who feeds the pet
Specific visitor roles: host, others
Abstract element character: Animal
Specific element roles: cat, dog
Structure object role: master home
//Abstract element role class public interface Animal { //Receive visitor access void accept(Person person); } //Concrete element class public class Dog implements Animal{ @Override public void accept(Person person) { person.feed(this); System.out.println("Woof, woof"); } } //Concrete element role class public class Cat implements Animal{ @Override public void accept(Person person) { person.feed(this); System.out.println("cat "); } } //Visitor role class public interface Person { //feed void feed(Cat cat); void feed(Dog dog); } //Specific visitors public class Owner implements Person{ @Override public void feed(Cat cat) { System.out.println("The owner feeds the cat"); } @Override public void feed(Dog dog) { System.out.println("The owner feeds the dog"); } } //Specific visitors public class Someone implements Person{ @Override public void feed(Cat cat) { System.out.println("Friends feed cats"); } @Override public void feed(Dog dog) { System.out.println("Friends feed dogs"); } } //Structure object public class Home { //Declare a collection object to store element objects private List<Animal> list = new ArrayList<>(); //Add element function public void add(Animal animal){ list.add(animal); } // public void action(Person person){ for (Animal animal:list){ animal.accept(person); } } } public class Client { public static void main(String[] args) { //Create home object Home home = new Home(); //Add element home.add(new Cat()); home.add(new Dog()); //Create master object Owner owner = new Owner(); home.action(owner); } }
Advantages and disadvantages
advantage
- It has good expansibility and adds new functions to the elements in the object structure without modifying the elements in the object structure
- Good reusability. The visitor defines the general functions of the whole object, so as to improve the degree of reusability
- Separate irrelevant behaviors, separate irrelevant behaviors through visitors, and encapsulate relevant behaviors to form a visitor, so that each visitor has a single function
shortcoming
- It is very difficult to change the object structure. In the visitor mode, without adding a new element class, the corresponding specific operation should be added to each specific visitor. This is the principle of opening and closing
- In violation of the dependency lead principle, the visitor pattern relies on concrete classes instead of abstract classes
Usage scenario
- The object structure is relatively stable, but its total algorithm often changes
- The objects in the object structure need to provide a variety of different and irrelevant operations, and the changes of these operations should not affect the structure of the object
Memo mode
Also called snapshot mode, it captures the internal state of an object without destroying the encapsulation, and saves the state outside the object, so that the object can be restored to the original saved state when necessary in the future
structure
Initiator role: it records the internal status information at the current time, provides the function of creating memo data, and implements a total of other businesses. It can access all the information in the memo
Memo role: it is responsible for storing the internal status of the initiator and providing these internal status to the initiator when necessary
Caretaker: it manages memos and provides the functions of protecting and obtaining memos, but it cannot access and modify the contents of memos
Narrow interface: the narrow interface of the memo when the manager object sees it. This narrow interface only allows him to pass the memo object to other objects
Wide interface: Contrary to the narrow interface seen by the management sister, the initiator object can see a wide interface that allows it to read all data and recover the internal state of the initiator object according to the data
Code example
Game play boss
White box memo mode (wide interface)
//Memo role class public class RoleStateMemento { private int vit; private int atk; private int def; public RoleStateMemento() { } public RoleStateMemento(int vit, int atk, int def) { this.vit = vit; this.atk = atk; this.def = def; } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getAtk() { return atk; } public void setAtk(int atk) { this.atk = atk; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } } //Game role class (initiator role) public class GameRole { private int vit;//Life value private int atk;//aggressivity private int def;//Defensive power //Initialize internal state public void initState(){ this.vit=100; this.atk=100; this.def=100; } //battle public void fight(){ this.vit=0; this.atk=0; this.def=0; } //Save role status function public RoleStateMemento saveState(){ return new RoleStateMemento(vit,atk,def); } //Restore role initialization state public void recoverState(RoleStateMemento roleStateMemento){ this.vit=roleStateMemento.getVit(); this.atk=roleStateMemento.getAtk(); this.def=roleStateMemento.getDef(); } //Display status public void stateDisplay(){ System.out.println("GameRole{" + "vit=" + vit + ", atk=" + atk + ", def=" + def + '}'); } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getAtk() { return atk; } public void setAtk(int atk) { this.atk = atk; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } } //Manager role public class RoleStateCaretaker { //Declare a variable of type rolestatemento private RoleStateMemento roleStateMemento; public RoleStateMemento getRoleStateMemento() { return roleStateMemento; } public void setRoleStateMemento(RoleStateMemento roleStateMemento) { this.roleStateMemento = roleStateMemento; } } public class Client { public static void main(String[] args) { System.out.println("----War boss front----"); GameRole gameRole = new GameRole(); gameRole.initState(); gameRole.stateDisplay(); //backups RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker(); roleStateCaretaker.setRoleStateMemento(gameRole.saveState()); System.out.println("----War boss after----"); gameRole.fight(); gameRole.stateDisplay(); System.out.println("----recovery----"); gameRole.recoverState(roleStateCaretaker.getRoleStateMemento()); gameRole.stateDisplay(); } }
In the black box memo mode (narrow interface), the way to realize the dual interface is to design the memo class into an internal member class that initiates human beings
//Memorandum interface, providing external narrow interface public interface Memento { } //Game role class (initiator role) public class GameRole { private int vit;//Life value private int atk;//aggressivity private int def;//Defensive power //Initialize internal state public void initState(){ this.vit=100; this.atk=100; this.def=100; } //battle public void fight(){ this.vit=0; this.atk=0; this.def=0; } //Save role status function public Memento saveState(){ return new RoleStateMemento(vit,atk,def); } //Restore role initialization state public void recoverState(Memento memento){ RoleStateMemento roleStateMemento = (RoleStateMemento)memento; this.vit=roleStateMemento.getVit(); this.atk=roleStateMemento.getAtk(); this.def=roleStateMemento.getDef(); } //Display status public void stateDisplay(){ System.out.println("GameRole{" + "vit=" + vit + ", atk=" + atk + ", def=" + def + '}'); } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getAtk() { return atk; } public void setAtk(int atk) { this.atk = atk; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } private class RoleStateMemento implements Memento{ private int vit;//Life value private int atk;//aggressivity private int def;//Defensive power public RoleStateMemento() { } public RoleStateMemento(int vit, int atk, int def) { this.vit = vit; this.atk = atk; this.def = def; } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getAtk() { return atk; } public void setAtk(int atk) { this.atk = atk; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } } } //Manager role public class RoleStateCaretaker { //Declare a variable of type rolestatemento private Memento roleStateMemento; public Memento getRoleStateMemento() { return roleStateMemento; } public void setRoleStateMemento(Memento roleStateMemento) { this.roleStateMemento = roleStateMemento; } } public class Client { public static void main(String[] args) { System.out.println("----War boss front----"); GameRole gameRole = new GameRole(); gameRole.initState(); gameRole.stateDisplay(); //backups RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker(); roleStateCaretaker.setRoleStateMemento(gameRole.saveState()); System.out.println("----War boss after----"); gameRole.fight(); gameRole.stateDisplay(); System.out.println("----recovery----"); gameRole.recoverState(roleStateCaretaker.getRoleStateMemento()); gameRole.stateDisplay(); } }
Advantages and disadvantages
advantage
- Provides a mechanism to restore state. When users need it, they can easily restore the data to a historical state
- The encapsulation of internal state is realized. Except for the initiator who created it, no other object can access this status information
- Simplified the human. The initiator does not need to manage and save each backup of its internal status. All status information is saved in the memo and managed by the manager, which is in line with the principle of single responsibility
shortcoming
Large resource consumption, if you want to save the internal
Usage scenario
- Scenes that need to save and restore data, such as the archiving function of intermediate results when playing games
- You need to provide a scenario for rollback operations, such as word, notepad and other software using Ctrl+Z, as well as transaction operations in the database
Interpreter mode
Define your own grammar
structure
Abstract expression role:
Define the interface of the interpreter and agree on the interpretation operation of the interpreter, mainly including the interpretation method interpret
Terminal expression role: it is a subclass of abstract expression, which is used to implement operations related to terminals in grammar. Each terminal in grammar has a specific mediator expression corresponding to it
Nonterminal expression role: it is also a subclass of abstract expression, which is used to implement operations related to non terminators in grammar. Each rule in grammar corresponds to a non intermediary expression
Context: it usually contains the data or common functions required by each interpreter. It is generally used to transfer the data shared by all interpreters. Subsequent interpreters can obtain these values from here
Client (Client): the main task is to translate the sentences or expressions that need analysis into the abstract syntax tree described by the interpreter object, then invoke the interpreter's interpretation method, and, of course, can access the interpreter's interpretation method through the introduction of the environment role.
Code example
Calculate a - (b+c)
//Abstract expression class public abstract class AbstractExpression { //Method of interpretation public abstract int interpret(Context context); } //Environment role class public class Context { //Define a map set to store variables and corresponding values private Map<Variable,Integer> map = new HashMap<>(); //Function of adding variables public void assign(Variable variable,Integer integer){ map.put(variable,integer); } //Obtain the corresponding value according to the variable public int getValue(Variable variable){ return map.get(variable); } } //Addition expression class public class Plus extends AbstractExpression{ //+Expression around sign private AbstractExpression left; private AbstractExpression right; public Plus(AbstractExpression left, AbstractExpression right) { this.left = left; this.right = right; } @Override public String toString() { return "Plus{" + "left=" + left + ", right=" + right + '}'; } @Override public int interpret(Context context) { //Add the results of the left and right expressions return left.interpret(context)+right.interpret(context); } } //Classes that encapsulate variables public class Variable extends AbstractExpression{ //Declare a member variable that stores the variable name private String name; public Variable(String name) { this.name = name; } @Override public String toString() { return "Variable{" + "name='" + name + '\'' + '}'; } @Override public int interpret(Context context) { return context.getValue(this); } } public class Minus extends AbstractExpression{ //-Expression around sign private AbstractExpression left; private AbstractExpression right; public Minus(AbstractExpression left, AbstractExpression right) { this.left = left; this.right = right; } @Override public String toString() { return "Plus{" + "left=" + left + ", right=" + right + '}'; } @Override public int interpret(Context context) { //Add the results of the left and right expressions return left.interpret(context)-right.interpret(context); } } public class Client { public static void main(String[] args) { //Create environment objects Context context = new Context(); //Create multiple variables Variable a = new Variable("a"); Variable b = new Variable("b"); Variable c = new Variable("c"); //Store variables in environment objects context.assign(a,1); context.assign(b,2); context.assign(c,3); //Get abstract syntax tree a+b-c AbstractExpression expression = new Minus(a,new Plus(b,c)); int interpret = expression.interpret(context); System.out.println(interpret); } }
Advantages and disadvantages
advantage
- Easy to change and expand grammar. Since classes are used in interpreter mode to represent the grammar rules of the language, the grammar can be changed or extended by inheriting hierarchy. Each grammar rule can be expressed as a class, so it is convenient to implement a simple language
- It is easier to implement grammar. In the abstract syntax tree, the implementation of each expression node class is similar, and the coding of these classes will not be particularly complex
- It is more convenient to add a new interpretation expression. If you need to add a new interpretation expression, you only need to add a new terminator expression or non terminator expression class accordingly. The original expression class code does not need to be modified and conforms to the opening and closing principle
shortcoming
- It is difficult to maintain complex grammar. In the interpreter mode, at least one class needs to be defined for each rule. Therefore, if a language contains too many grammar rules, the number of classes will increase sharply, making it difficult for the system to manage and maintain
- Low execution efficiency. Because a large number of loops and recursive calls are used in the interpreter mode, the speed of interpreting more complex sentences is very slow, and the modulation process of the code is also troublesome
Usage scenario
- When the grammar of the language is relatively simple and the execution efficiency is not the key problem
- When the problem occurs repeatedly and can be expressed in a simple language
- When a language needs interpretation and execution, and the sentences in the language can be expressed as an abstract syntax tree
Comprehensive exercise
Analysis of Spring IOC related interfaces
BeanFactory parsing
As the top-level interface, BeanFactory defines the basic functional specifications of IOC container
Beanfactory has three important sub interfaces:
ListableBeanFactory
HierarchicalBeanFactory
AutowireCapableBeanFactory
The final default implementation class is DefaultListableBeanFactory
Why define so many interfaces
Each interface has its application occasion, which is mainly to distinguish the transfer and transformation of objects and the restrictions on object data access during the internal operation of spring, such as
- The ListableBeanFactory interface indicates that these beans can be listed (stored)
- Hierarchical beanfactory indicates that these beans are inherited, that is, each Bean may have a parent Bean
- The AutowireCapableBeanFactory interface defines the automatic assembly rules of beans
BeanFactory has a very important sub interface, the ApplicationContext interface, which is mainly used to regulate that the bean objects in the container are loaded without delay, that is, the bean is initialized and stored in the container when the container object is created
Implementation of IOC container provided by Spring:
- ClasspathXmlApplicationContext: loads the xml configuration file according to the classpath and creates an IOC container
- FileSystemXmlApplicationContext: load the xml configuration file according to the system path and create the IOC container
- AnnotationConfigApplicationContext: loads the annotation class configuration and creates an IOC container
BeanDefinition parsing
Bean objects are described by BeanDefinition in the Spring implementation
BeanDefinitionReader parsing
BeanDefinitionReader is used to parse Spring configuration files to obtain Bean objects
BeanDefinitionRegistry parsing
BeanDefinitionReader is used to parse bean definitions and encapsulate them into BeanDefinition objects. Many bean tags are defined in the configuration file we define. These beans are parsed and sent to the BeanDefinition registry, and the top-level interface of the registry is BeanDefinitionRegistry
Customize spring IOC
<beans> <bean id="userService" class="com.xx.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <bean id="userDao" class="com.xx.userDao"></bean> </beans>
Define bean related POJOs
Define the PropertyValue class, corresponding to the property tag object in the bean tag
/** * Used to encapsulate the properties of the property tag under the bean tag * name attribute * ref attribute * value attribute */ public class PropertyValue { private String name; private String ref; private String value; public PropertyValue() { } public PropertyValue(String name, String ref, String value) { this.name = name; this.ref = ref; this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRef() { return ref; } public void setRef(String ref) { this.ref = ref; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
A bean tag can have multiple property sub tags, so define a MutablePropertyValues class to store and manage multiple PropertyValue objects
/** * Used to store and manage multiple PropertyValue objects */ public class MutablePropertyValues implements Iterable<PropertyValue> { //Define a list object to store the PropertyValue object private final List<PropertyValue> propertyValues; public MutablePropertyValues(){ propertyValues = new ArrayList<>(); } public MutablePropertyValues(List<PropertyValue> propertyValues) { if (propertyValues == null) { this.propertyValues = new ArrayList<>(); } else { this.propertyValues = propertyValues; } } //Get all PropertyValue objects and return in the form of array public PropertyValue[] getPropertyValues(){ return propertyValues.toArray(new PropertyValue[0]); } //Gets the PropertyValue object based on the name property value public PropertyValue getPropertyValue(String propertyName){ for (PropertyValue propertyValue : propertyValues){ if(propertyValue.getName().equals(propertyName)){ return propertyValue; } } return null; } //Determine whether the collection is empty public boolean isEmpty(){ return propertyValues.isEmpty(); } //Add PropertyValue object public MutablePropertyValues addPropertyValue(PropertyValue propertyValue){ //Determine whether the propertyValue object stored in the collection duplicates the one passed in for (int i = 0; i < propertyValues.size(); i++) { PropertyValue propertyValue1 = propertyValues.get(i); if(propertyValue.getName().equals(propertyValue1.getName())){ propertyValues.set(i,propertyValue); return this; } } propertyValues.add(propertyValue); return this; } //Judge whether there is an object with the specified name attribute value public boolean contains(String propertyName){ return getPropertyValue(propertyName)!=null; } //Get iterator object @Override public Iterator<PropertyValue> iterator() { return propertyValues.iterator(); } }
BeanDefinition class is used to encapsulate bean information, mainly including id, class, property and other attributes and tags
/** * Used to encapsulate bean tag data * id attribute * class attribute * property Sub label */ public class BeanDefinition { private String id; private String className; private MutablePropertyValues mutablePropertyValues; public BeanDefinition(){ mutablePropertyValues = new MutablePropertyValues(); } public BeanDefinition(String id, String className, MutablePropertyValues mutablePropertyValues) { this.id = id; this.className = className; this.mutablePropertyValues = mutablePropertyValues; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public MutablePropertyValues getMutablePropertyValues() { return mutablePropertyValues; } public void setMutablePropertyValues(MutablePropertyValues mutablePropertyValues) { this.mutablePropertyValues = mutablePropertyValues; } }
Define registry related classes
Create the BeanDefinitionRegistry interface and define the relevant operations of the registry, as follows:
/** * Registry object class, registry related operations */ public interface BeanDefinitionRegistry { //Register the BeanDefinition object into the registry void registerBeanDefinition(String beanName,BeanDefinition beanDefinition); //Deletes the BeanDefinition object with the specified name from the registry void removeBeanDefinition(String beanName) throws Exception; //Gets the BeanDefinition object from the registry by name BeanDefinition getBeanDefinition(String beanName) throws Exception; //Determines whether the registry contains a BeanDefinition object with the specified name boolean containsBeanDefinition(String beanName); //Gets the number of BeanDefinition objects in the registry int getBeanDefinitionCount(); //Gets the names of all beandefinitions in the registry String[] getBeanDefinitionName(); }
The SimpleBeanDefinitionRegistry class implements the BeanDefinitionRegistry interface and defines the Map collection as the registry container
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry{ private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(); @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) { beanDefinitionMap.put(beanName,beanDefinition); } @Override public void removeBeanDefinition(String beanName) throws Exception { beanDefinitionMap.remove(beanName); } @Override public BeanDefinition getBeanDefinition(String beanName) throws Exception { return beanDefinitionMap.get(beanName); } @Override public boolean containsBeanDefinition(String beanName) { return beanDefinitionMap.containsKey(beanName); } @Override public int getBeanDefinitionCount() { return beanDefinitionMap.size(); } @Override public String[] getBeanDefinitionName() { return beanDefinitionMap.keySet().toArray(new String[0]); } }
Define parser related classes
The BeanDefinitionReader interface is used to parse the configuration file and register the bean information in the registry
public interface BeanDefinitionReader { //Get registry object BeanDefinitionRegistry getRegistry(); //Load the configuration file and register in the registry void loadBeanDefinitions(String configLocation) throws Exception; }
XmlBeanDefinitionReader class is specially used to parse xml configuration files. This class implements BeanDefinitionReader interface and two functions in the interface
/** * Parsing the xml configuration file */ public class XmlBeanDefinitionReader implements BeanDefinitionReader{ //Declare registry objects private BeanDefinitionRegistry registry; public XmlBeanDefinitionReader() { this.registry = new SimpleBeanDefinitionRegistry(); } @Override public BeanDefinitionRegistry getRegistry() { return registry; } @Override public void loadBeanDefinitions(String configLocation) throws Exception { //Parsing xml configuration files using dom4j SAXReader reader = new SAXReader(); //Get the configuration file under the classpath InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation); Document document = reader.read(is); //Gets the root label object from the Document object Element rootElements = document.getRootElement(); //Get all bean tags under the root tag List<Element> beanElements = rootElements.element("bean"); //Traversal set for(Element element : beanElements){ String id = element.attributeValue("id"); String className = element.attributeValue("class"); BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setId(id); beanDefinition.setClassName(className); //establish MutablePropertyValues mutablePropertyValues = new MutablePropertyValues(); //Get all property objects under the bean tag List<Element> propertyElements = rootElements.element("property"); for(Element pElement : propertyElements){ String name = pElement.attributeValue("name"); String ref = pElement.attributeValue("ref"); String value = pElement.attributeValue("value"); PropertyValue propertyValue = new PropertyValue(name, ref, value); mutablePropertyValues.addPropertyValue(propertyValue); } //Encapsulate MutablePropertyValues object into beanDefinition object beanDefinition.setMutablePropertyValues(mutablePropertyValues); //Register beanDefinition in the registry registry.registerBeanDefinition(id,beanDefinition); } } }
IOC container related classes
BeanFactory interface
Define the unified specification of IOC container in this interface, that is, get bean object
public interface BeanFactory { //Get the bean object according to the name of the bean object Object getBean(String name) throws Exception; //Obtain the bean object according to the name of the bean object and perform type conversion <T> T getBean(String name,Class<? extends T>clazz) throws Exception; }
ApplicationContext interface
All sub implementation classes of the interface create bean objects without delay, so the refresh method is defined in the interface
/** * Define non delayed loading function */ public interface ApplicationContext extends BeanFactory{ void refresh() throws Exception; }
AbstractApplicationContext class
- As a subclass of the ApplicationContext interface, this class is also non delayed loading, so you need to define a map collection in this class as a container for bean object storage
- Declare variables of BeanDefinitionReader type, which are used to parse xml configuration files, and comply with the principle of single responsibility. The creation of objects of BeanDefinitionReader type is implemented by subclasses, because only subclasses specify which subclass of BeanDefinitionReader is created to implement the object
/** * ApplicationContext Sub implementation class of interface * For immediate loading */ public abstract class AbstractApplicationContext implements ApplicationContext{ protected BeanDefinitionReader beanDefinitionReader; //Container for storing bean objects protected Map<String,Object> singletonObjects = new HashMap<>(); //Variables that declare the configuration file path protected String configLocation; @Override public void refresh() throws Exception { //Load BeanDefinition object beanDefinitionReader.loadBeanDefinitions(configLocation); //Initialize bean finishBeanInitialization(); } //bean initialization private void finishBeanInitialization() throws Exception{ //Get registry object BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); //Get beanDefinition object String[] beanNames = registry.getBeanDefinitionName(); for (String beanName : beanNames){ //Initialize the bean getBean(beanName); } } }
ClassPathXmlApplicationContext class
This class is mainly used to load the configuration file under the class path and create bean objects. It mainly completes the following functions:
- In the constructor, create the BeanDefinitionReader object
- In the construction method, the refresh method is invoked to load the configuration file, create the bean object and store it in the container.
- Rewrite the getBean method in the parent interface and implement the dependency injection operation
/** * IOC Container specific subclasses * It is used to load the configuration file in xml format under the classpath */ public class ClassPathXmlApplicationContext extends AbstractApplicationContext { public ClassPathXmlApplicationContext(String configLocation){ this.configLocation=configLocation; //Building parser objects beanDefinitionReader = new XmlBeanDefinitionReader(); try{ refresh(); }catch(Exception e){ e.printStackTrace(); } } //Get the bean object according to the name of the bean object public Object getBean(String name) throws Exception{ //Determines whether the bean object with the specified name is contained in the object container Object o = singletonObjects.get(name); if(o != null){ return o; } //Get beanDefinition object BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); BeanDefinition beanDefinition = registry.getBeanDefinition(name); //Get class in bean String className = beanDefinition.getClassName(); //Create objects with reflections Class<?> aClass = Class.forName(className); Object o1 = aClass.newInstance(); //Dependency injection MutablePropertyValues mutablePropertyValues = beanDefinition.getMutablePropertyValues(); for (PropertyValue propertyValue : mutablePropertyValues){ String pName = propertyValue.getName(); String pValue = propertyValue.getValue(); String pRef = propertyValue.getRef(); if(pRef!=null && "".equals(pRef)){ //Get the dependent bean object Object bean = getBean(pRef); //Splicing method name String methodName = StringUtils.getSetterMethodName(pName); //Get all method objects Method[] methods = aClass.getMethods(); for (Method method:methods){ if(methodName.equals(method.getName())){ method.invoke(o1,bean); } } } if(pValue!=null && "".equals(pValue)){ //Splicing method name String methodName = StringUtils.getSetterMethodName(pName); Method method = aClass.getMethod(methodName,String.class); method.invoke(o1,pValue); } } //Add the container before returning the object singletonObjects.put(name,o1); return o1; } public <T> T getBean(String name,Class<? extends T> clazz) throws Exception{ Object bean = getBean(name); if(bean == null){ return null; } return clazz.cast(bean); } }