Design mode
Using design patterns can make our code have better readability, scalability, readability, reusability and meet the characteristics of high cohesion and low coupling. As programmers, it is a concept we often hear, and it is also a knowledge that we programmers must deeply study and understand.
Types of design patterns
The table and figure are derived from Rookie tutorial
Serial number | Mode & Description | include |
---|---|---|
1 | Creative patterns these design patterns provide a way to hide the creation logic while creating objects, rather than directly instantiating objects using the new operator. This makes the program more flexible in determining which objects need to be created for a given instance. | Factory Pattern Abstract Factory Pattern Singleton Pattern Builder Pattern Prototype Pattern |
2 | Structural patterns these design patterns focus on the combination of classes and objects. The concept of inheritance is used to combine interfaces and define how composite objects get new functions. | Adapter Pattern Bridge Pattern Filter pattern Composite Pattern Decorator Pattern Facade Pattern Flyweight Pattern Proxy Pattern |
3 | Behavioral patterns these design patterns pay particular attention to the communication between objects. | Chain of Responsibility Pattern Command Pattern Interpreter Pattern Iterator Pattern Mediator Pattern memo pattern Observer Pattern State Pattern Null Object Pattern Strategy Pattern Template Pattern Visitor Pattern |
4 | J2EE Patterns these design patterns pay special attention to the presentation layer. These patterns are certified by Sun Java Center. | MVC Pattern Business Delegate Pattern Composite Entity Pattern Data Access Object Pattern Front Controller Pattern Intercepting Filter Pattern Service Locator Pattern Transfer Object Pattern |
The following is a picture to describe the relationship between design patterns as a whole:
Seven principles
1. Single Responsibility Principle
Functional correlation between the constituent elements of the module. A class is responsible for only one responsibility.
2. Liskov Substitution Principle
One of the basic principles of object-oriented design. Where any base class can appear, subclasses must appear, and derived classes can also add new behaviors on the basis of the base class. The specification is set for chicken ribs. Although it is not mandatory for subclasses to follow these contracts, it cannot be modified arbitrarily, which will reduce the portability of the program and increase the coupling of the program (the modification of the base class will affect all subclasses)
3. Dependency Inversion Principle
This principle is the basis of the opening and closing principle, which depends on abstraction rather than concrete. The core idea is interface oriented programming.
4. Interface Segregation Principle
You can increase the number of interfaces to ensure that one class depends on the minimum interface of another class. You should establish special interfaces for each class. For example, if B depends on the 1 and 2 interfaces of A and C depends on the 3 and 4 interfaces of A, the 1 and 2 interfaces can be isolated as new interfaces.
5. Dimitri's law, also known as the least known principle
An entity should interact with other entities as little as possible to make the system functional modules relatively independent.
6. Composite Reuse Principle
Try to use composition / aggregation instead of inheritance.
7. Open Close Principle
It is open to extensions and closed to modifications. Through the use of interfaces and abstract classes, when the program needs to be expanded, the original code cannot be modified to achieve a hot plug effect.
Single responsibility principle
The single responsibility principle represents the functional correlation between the constituent elements of the module. A class is responsible for only one responsibility.
At first, there was only one type of animals in the business, herbivores. Our code was written like this.
package com.wangscaler.singleresponsibility; /** * @author wangscaler * @date 2021.06.16 10:25 */ public class SingleResponsibilityPrinciple { public static void main(String[] args) { Animal animal = new Animal(); animal.eat("cattle"); animal.eat("sheep"); animal.eat("horse"); } } class Animal { public void eat(String type) { System.out.println(type + "graze"); } }
This is absolutely no problem. With the growth of business, we find that we have increased carnivores. If we write animal directly Eat ("tiger"); This violates the principle of our single responsibility and is obviously inappropriate. Then our code needs to be rewritten.
The correct case 1 is as follows:
package com.wangscaler.singleresponsibility; /** * @author wangscaler * @date 2021.06.16 10:25 */ public class SingleResponsibilityPrinciple { public static void main(String[] args) { Carnivorous carnivorous = new Carnivorous(); Herbivorous herbivorous = new Herbivorous(); herbivorous.eat("cattle"); herbivorous.eat("sheep"); herbivorous.eat("horse"); carnivorous.eat("tiger"); carnivorous.eat("lion"); } } class Herbivorous { public void eat(String type) { System.out.println(type + "graze"); } } class Carnivorous { public void eat(String type) { System.out.println(type + "eat meat"); } }
This change obviously conforms to the principle of single responsibility. When there are many methods under the class, they can be used. There are few methods here. If you write this way, it will cost a lot. Therefore, you can break the single responsibility principle of the class and keep the method under the single responsibility principle.
Correct case 2:
package com.wangscaler.singleresponsibility; /** * @author wangscaler * @date 2021.06.16 10:25 */ public class SingleResponsibilityPrinciple2 { public static void main(String[] args) { Animals animals = new Animals(); animals.herbivorousEat("cattle"); animals.carnivorousEat("tiger"); } } class Animals { public void herbivorousEat(String type) { System.out.println(type + "graze"); } public void carnivorousEat(String type) { System.out.println(type + "eat meat"); } }
In this way, when we add categories, we only need to add new categories, and the previous code does not need to be moved. For example, at this time, we increase animals that eat both meat and grass. Just modify it like this.
package com.wangscaler.singleresponsibility; /** * @author wangscaler * @date 2021.06.16 10:25 */ public class SingleResponsibilityPrinciple2 { public static void main(String[] args) { Animals animals = new Animals(); animals.herbivorousEat("cattle"); animals.carnivorousEat("tiger"); //Don't worry about whether the panda eats meat and grass. The stick essence detours. animals.eat("panda"); } } class Animals { public void herbivorousEat(String type) { System.out.println(type + "graze"); } public void carnivorousEat(String type) { System.out.println(type + "eat meat"); } public void eat(String type) { System.out.println(type + "Eat both meat and grass"); } }
We can see that now we only need to add new business code, and the code written before has no impact.
Summary: the key of the single responsibility principle is to separate the different responsibilities of classes in the business into different classes or interfaces. The principle is a method to deal with a responsibility as much as possible. Of course, responsibilities must be related, which needs to be divided and isolated according to business and requirements. If there are many methods, it is better to isolate them on the class. If there are few methods and the logic is simple, you can make the class violate the principle of single responsibility and keep the principle.
Interface isolation principle
The number of interfaces can be increased to ensure that one class depends on the minimum interface of another class. To establish the special interface required for each class, the interface should be as detailed as possible. One interface corresponds to A function module. At the same time, the methods in the interface should be as few as possible to make the interface more portable and flexible. For example, if B depends on the 1 and 2 interfaces of A and C depends on the 3 and 4 interfaces of A, the 1 and 2 interfaces can be isolated as new interfaces; 3. 4 isolate it as A new interface.
Examples of errors:
At first, our interface wrote all behaviors, but the horse only depended on eat and run; Ducks depend on eat, run and swim; Swans depend on all methods. For Malay, the fly method is obviously useless, and the method must also be implemented, which will be illogical.
package com.wangscaler.interfacesegregation; /** * @author wangscaler * @date 2021.06.16 16:35 */ public class InterfaceSegregationPrinciple { public static void main(String[] args) { IAction horse = new Horse(); horse.eat(); } interface IAction { void eat(); void fly(); void run(); void swim(); } static class Horse implements IAction { public void eat() { System.out.println("Horses can eat"); } public void fly() { } public void run() { System.out.println("The horse will go"); } public void swim() { } } class Duck implements IAction { public void eat() { System.out.println("Ducks can eat"); } public void fly() { } public void run() { System.out.println("Ducks can walk"); } public void swim() { System.out.println("Ducks can swim"); } } class swan implements IAction { public void eat() { System.out.println("Swans can eat"); } public void fly() { System.out.println("Swans can fly"); } public void run() { System.out.println("Swans can go"); } public void swim() { System.out.println("Swans can swim"); } } }
We found that all animals have eat and run behaviors, while fly and swim are unique behaviors. Therefore, the interface is changed according to the interface isolation principle as follows:
Correct example.
package com.wangscaler.interfacesegregation; /** * @author wangscaler * @date 2021.06.16 16:35 */ public class InterfaceSegregationPrinciple1 { public static void main(String[] args) { Horse horse = new Horse(); horse.eat(); horse.run(); } interface IEatAndRunAction { void eat(); void run(); } interface IFlyAction { void fly(); } interface ISwimAction { void swim(); } static class Horse implements IEatAndRunAction { public void eat() { System.out.println("Horses can eat"); } public void run() { System.out.println("The horse will go"); } } class Duck implements IEatAndRunAction, ISwimAction { public void eat() { System.out.println("Ducks can eat"); } public void run() { System.out.println("Ducks can walk"); } public void swim() { System.out.println("Ducks can swim"); } } class swan implements IEatAndRunAction, ISwimAction, IFlyAction { public void eat() { System.out.println("Swans can eat"); } public void fly() { System.out.println("Swans can fly"); } public void run() { System.out.println("Swans can go"); } public void swim() { System.out.println("Swans can swim"); } } }
**Summary: * * the granularity of the interface must be reasonable. Too small will lead to more interfaces, too large will lead to reduced flexibility, reduce code redundancy and improve the cohesion of the system.
This paper consists of Jane Yue simprad Transcoding, original address juejin.cn
In design mode 1, we have talked about two design principles, and then continue to explain. This paper explains the Dependence Inversion Principle and Richter substitution principle.
stay Design mode I We have talked about two design principles, and we will continue to explain them.
Dependence Inversion Principle
It depends on abstraction rather than concrete, and the core idea is interface oriented programming. The purpose is to formulate a good specification (Design), not to realize it.
For example, the code we write now needs to be automatically deployed to the server, and start the automatic deployment using github. The implementation process is that programmers -- > submit code to github – > automated deployment.
Programmer -- > submit code
github – > Automated Deployment
Error example
package com.wangscaler.dependenceinversion; /** * @author wangscaler * @date 2021.06.16 17:43 */ public class DependenceInversionPrinciple { public static void main(String[] args) { Programmer programmer = new Programmer(); programmer.commit(new Github()); } static class Github { public String cicd() { return "github Automated Deployment complete"; } } static class Programmer { public void commit(Github github) { System.out.println(github.cicd()); } } }
One day, the access to github warehouse was too slow. We didn't want to use it. We replaced it with Gitlab. At this time, we built a Gitlab warehouse and added the cicd method. However, although we have this warehouse, we can't deploy it automatically, because our programmers only know github. At this time, Programmer relies on github, which is unreasonable. The coupling between modules is too high and the productivity is too low.
Correct example
package com.wangscaler.dependenceinversion; /** * @author wangscaler * @date 2021.06.16 17:43 */ public class DependenceInversionPrinciple1 { public static void main(String[] args) { Programmer programmer = new Programmer(); programmer.commit(new Gitlab()); } static class Github implements IWarehouse { public String cicd() { return "github Automated Deployment complete"; } } static class Gitlab implements IWarehouse { public String cicd() { return "gitlab Automated Deployment complete"; } } public interface IWarehouse { public String cicd(); } static class Programmer { public void commit(IWarehouse warehouse) { System.out.println(warehouse.cicd()); } } }
Because both github and gitlab belong to the warehouse, and both have cicd methods, it's OK to define the warehouse interface and let them implement it. If you want to change to Huawei cloud warehouse later, you only need to add Huawei cloud.
package com.wangscaler.dependenceinversion; /** * @author wangscaler * @date 2021.06.16 17:43 */ public class DependenceInversionPrinciple1 { public static void main(String[] args) { Programmer programmer = new Programmer(); programmer.commit(new Huawei()); } static class Github implements IWarehouse { public String cicd() { return "github Automated Deployment complete"; } } static class Gitlab implements IWarehouse { public String cicd() { return "gitlab Automated Deployment complete"; } } static class Huawei implements IWarehouse { public String cicd() { return "Huawei cloud warehouse automation deployment completed"; } } public interface IWarehouse { public String cicd(); } static class Programmer { public void commit(IWarehouse warehouse) { System.out.println(warehouse.cicd()); } } }
Conclusion: according to the dependency inversion principle, we focus on abstraction rather than concrete. In this example, our design idea should be that programmers -- > submit code to the warehouse -- > automatic deployment. Whether it's github, gitlab or Huawei cloud, they abstract it as a warehouse, that is, start with the lower module github. Think about what it can abstract. This abstract class is equivalent to a buffer layer to enhance the scalability of the code.
Richter substitution principle
All references to the base class can use its subclasses transparently. One of the basic principles of object-oriented design. Where any base class can appear, subclasses must appear, and derived classes can also add new behaviors on the basis of the base class. It will reduce the portability of the program and increase the coupling of the program (the modification of the base class will affect all subclasses). When a subclass inherits from the parent class, try not to override the methods of the parent class.
For example, we have two birds, swallows and exotic birds (can't fly). Originally, we know that birds fly, so the code is written like this
package com.wangscaler.liskovsubstitution; /** * @author wangscaler * @date 2021.06.17 */ public class LiskovSubstitutionPrinciple { public static void main(String[] args) { Swallow swallow = new Swallow(); Kiwi kiwi = new Kiwi(); swallow.setSpeed(110); kiwi.setSpeed(120); System.out.println(swallow.getFlyTime(240)); System.out.println(kiwi.getFlyTime(240)); } static class Bird { double speed; public void setSpeed(double speed) { this.speed = speed; } public double getFlyTime(double distance) { return (distance / speed); } } static class Swallow extends Bird { } static class Kiwi extends Bird { @Override public void setSpeed(double speed) { speed = 0; } } }
results of enforcement
2.1818181818181817 Infinity
Because the strange bird can't fly, it rewrites the method of the parent class, which leads us to set the speed in the parent class. Of course, it is the same to set the speed in the strange bird, which eventually leads to errors. We should set a more basic parent class to avoid rewriting the method of the parent class during word inheritance.
package com.wangscaler.liskovsubstitution; /** * @author wangscaler * @date 2021.06.17 */ public class LiskovSubstitutionPrinciple { public static void main(String[] args) { Swallow swallow = new Swallow(); Kiwi kiwi = new Kiwi(); swallow.setFlySpeed(110); kiwi.setSpeed(120); System.out.println(swallow.getFlyTime(240)); System.out.println(kiwi.getTime(240)); } static class Animal { double speed; public void setSpeed(double speed) { this.speed = speed; } public double getTime(double distance) { return (distance / speed); } } static class Bird extends Animal { double flyspeed; public void setFlySpeed(double speed) { this.flyspeed = speed; } public double getFlyTime(double distance) { return (distance / flyspeed); } } static class Swallow extends Bird { } static class Kiwi extends Animal { } }
Conclusion: the Richter substitution principle requires us to integrate the base class and try not to rewrite the parent class, so as to increase functions. If you must override the parent method, you must meet the requirements that the input conditions are more relaxed than the parent and the output conditions are more stringent than the parent. For example, in the initial code, the input condition of the parent class Bird is the range, while the input condition of the child class Kiwi becomes 0, which obviously does not comply with the rules.
Opening and closing principle
The most important and basic principle is open to expansion and closed to modification. Through the use of interfaces and abstract classes, when the program needs to be expanded, the original code cannot be modified to achieve a hot plug effect.
For example, we draw graphics. At first, we only draw rectangles and circles. The code is written like this
package com.wangscaler.openclose; /** * @author wangscaler * @date 2021.06.17 11:14 */ public class OpenClosePrinciple { public static void main(String[] args) { Graphic graphic = new Graphic(); graphic.drawRectangle(new Rectangle()); graphic.drawCircular(new Circular()); } static class Graphic { public void drawShape(Shape shape) { if (shape.type == 1) { drawCircular(shape); } else if (shape.type == 2) { drawRectangle(shape); } } public void drawRectangle(Shape shape) { System.out.println("draw rectangle..."); } public void drawCircular(Shape shape) { System.out.println("Draw circle..."); } } static class Shape { int type; } static class Circular extends Shape { Circular() { super.type = 1; } } static class Rectangle extends Shape { Rectangle() { super.type = 2; } } }
When we add a new graphic trapezoid, the code has to be changed in this way
Wrong way
package com.wangscaler.openclose; /** * @author wangscaler * @date 2021.06.17 11:14 */ public class OpenClosePrinciple1 { public static void main(String[] args) { Graphic graphic = new Graphic(); graphic.drawRectangle(new Rectangle()); graphic.drawCircular(new Circular()); graphic.drawTrapezoid(new Trapezoid()); } static class Graphic { public void drawShape(Shape shape) { if (shape.type == 1) { drawCircular(shape); } else if (shape.type == 2) { drawRectangle(shape); } else if(shape.type == 3){ drawTrapezoid(shape); } } public void drawRectangle(Shape shape) { System.out.println("draw rectangle..."); } public void drawCircular(Shape shape) { System.out.println("Draw circle..."); } public void drawTrapezoid(Shape shape) { System.out.println("Draw trapezoid..."); } } static class Shape { int type; } static class Circular extends Shape { Circular() { super.type = 1; } } static class Rectangle extends Shape { Rectangle() { super.type = 2; } } static class Trapezoid extends Shape { Trapezoid() { super.type = 3; } } }
In this way, we can get the correct results, but there are many modified codes, which can not achieve the hot plug effect. We not only modified the code of the provider (added tradezoid), but also modified the code of the user (Graphic). We should turn Shape into an abstract class. When there is a new Shape, we only need to add a new Shape class to inherit this abstract class
The right way
package com.wangscaler.openclose; /** * @author wangscaler * @date 2021.06.17 11:14 */ public class OpenClosePrinciple2 { public static void main(String[] args) { Graphic graphic = new Graphic(); graphic.drawShape(new Rectangle()); graphic.drawShape(new Circular()); } static class Graphic { public void drawShape(Shape shape) { shape.draw(); } } static abstract class Shape { public abstract void draw(); } static class Circular extends Shape { @Override public void draw() { System.out.println("Draw circle..."); } } static class Rectangle extends Shape { @Override public void draw() { System.out.println("draw rectangle..."); } } }
At this time, we add a new graphic rectangle. We just need to modify it to
package com.wangscaler.openclose; /** * @author wangscaler * @date 2021.06.17 11:14 */ public class OpenClosePrinciple3 { public static void main(String[] args) { Graphic graphic = new Graphic(); graphic.drawShape(new Rectangle()); graphic.drawShape(new Circular()); graphic.drawShape(new Trapezoid()); } static class Graphic { public void drawShape(Shape shape) { shape.draw(); } } static abstract class Shape { public abstract void draw(); } static class Circular extends Shape { @Override public void draw() { System.out.println("Draw circle..."); } } static class Rectangle extends Shape { @Override public void draw() { System.out.println("draw rectangle..."); } } static class Trapezoid extends Shape { @Override public void draw() { System.out.println("Draw trapezoid..."); } } }
Summary: the opening and closing principle is open to expansion and closed to modification. We only need to add a new trapezoid class. Let our trapezoid inherit the shape abstract class and implement the methods of the abstract class. At this time, we do not need to modify the original code to achieve the results we want.
Dimitt's law
An entity should interact with other entities as little as possible to make the system functional modules relatively independent. That is, only communicate with direct friends (method parameters, member variables, method return values).
For example, the school principal wants to check the number of people in a class, so
package com.wangscaler.leastknowledgeprinciple; import java.util.ArrayList; import java.util.List; /** * @author wangscaler * @date 2021.06.17 14:09 */ public class LeastKnowledgePrinciple { public static void main(String[] args) { Principal principal = new Principal(); principal.commond(new Teacher()); } static class Principal { public void commond(Teacher teacher) { List<Student> students = new ArrayList<Student>(); for (int i = 0; i < 20; i++) { students.add(new Student()); } teacher.count(students); } } static class Teacher { public void count(List<Student> students) { System.out.println("The number of students is:" + students.size()); } } static class Student { } }
Here, we have a Student in the Principal's common method. He is not a method parameter, member variable or method return value. Therefore, Student is not a direct friend of Principal, which is a violation of Demeter's law. So how to modify it, because Student and Teacher are direct friends
package com.wangscaler.leastknowledgeprinciple; import java.util.ArrayList; import java.util.List; /** * @author wangscaler * @date 2021.06.17 14:09 */ public class LeastKnowledgePrinciple1 { public static void main(String[] args) { List<Student> students = new ArrayList<Student>(); for (int i = 0; i < 20; i++) { students.add(new Student()); } Principal principal = new Principal(); principal.commond(new Teacher(students)); } static class Principal { public void commond(Teacher teacher) { teacher.count(); } } static class Teacher { private List<Student> students; public Teacher(List<Student> students) { this.students = students; } public void count() { System.out.println("The number of students is:" + students.size()); } } static class Student { } }
Students become members of teachers. Teachers and students are direct friends, and principals and teachers are direct friends.
Summary: as can be seen from the above examples, the Dimitri law can reduce the coupling. Try to avoid non direct friends (local variables) in the class.
We have talked about six design principles of design patterns: single responsibility principle, interface isolation principle, dependency inversion principle, Richter substitution principle, opening and closing principle and Dimiter's principle. This paper will introduce the last synthetic Reuse Principle
We have talked about six design principles, Single responsibility principle, interface isolation principleDependency inversion principle, Richter substitution principle Opening and closing principle and Dimitri's law this article will introduce the last synthetic Reuse Principle and three ways of dependency transmission.
Synthetic Reuse Principle
Try to use composition / aggregation instead of inheritance.
At the beginning, A has two methods, and B just needs these two methods. At this time, the most direct way is inheritance.
package com.wangscaler.compositereuseprinciple; /** * @author wangscaler * @date 2021.06.17 14:50 */ public class CompositeReusePrinciple { public static void main(String[] args) { B b = new B(); b.test(); } static class A { void test() { System.out.println("test"); } void test1() { System.out.println("test1"); } } static class B extends A { } }
In this way, we simply let B have the functions of test and test1. However, with the development of business, A adds test2 and test3 methods, which is redundant for B, which greatly increases the coupling. Therefore, according to the principle of synthesis and reuse, there are three methods to modify the code as follows.
Method 1: synthesis
package com.wangscaler.compositereuseprinciple; /** * @author wangscaler * @date 2021.06.17 14:50 */ public class CompositeReusePrinciple1 { public static void main(String[] args) { B b = new B(); b.test(); } static class A { void test() { System.out.println("test"); } void test1() { System.out.println("test1"); } } static class B { A a = new A(); void test() { a.test(); } } }
Mode 2: dependency
package com.wangscaler.compositereuseprinciple; /** * @author wangscaler * @date 2021.06.17 14:50 */ public class CompositeReusePrinciple2 { public static void main(String[] args) { B b = new B(); b.test(new A()); } static class A { void test() { System.out.println("test"); } void test1() { System.out.println("test1"); } } static class B { void test(A a) { a.test(); } void test1(A a) { a.test1(); } } }
Mode 3: aggregation
package com.wangscaler.compositereuseprinciple; /** * @author wangscaler * @date 2021.06.17 14:50 */ public class CompositeReusePrinciple3 { public static void main(String[] args) { B b = new B(); b.setA(new A()); b.test(); } static class A { void test() { System.out.println("test"); } void test1() { System.out.println("test1"); } } static class B { private A a; public void setA(A a) { this.a = a; } void test() { a.test(); } } }
Summary: give priority to synthesis / aggregation and finally inheritance. Aggregation composition is a kind of "black box" reuse, while inheritance is a white box, which is transparent to subclasses.
Three ways of dependency transmission
- Interface transfer
- Construction method transfer
- setter mode transmission
Interface transfer
package com.wangscaler.dependencytransfer; /** * @author wangscaler * @date 2021.06.17 17:14 */ public class DependencyTransfer { public static void main(String[] args) { Driver driver = new Driver(); driver.drive(new Civic()); } interface IDriver { void drive(ICar car); } interface ICar { void running(); } static class Civic implements ICar { public void running() { System.out.println("Civic second day second earth second air"); } } static class Driver implements IDriver { public void drive(ICar car) { car.running(); } } }
In this example, we pass ICar in the interface IDriver, and the Driver obtains the function of car running by implementing IDriver
Construction method transfer
package com.wangscaler.dependencytransfer; /** * @author wangscaler * @date 2021.06.17 17:14 */ public class DependencyTransfer1 { public static void main(String[] args) { Driver driver = new Driver(new Civic()); driver.drive(); } interface IDriver { void drive(); } interface ICar { void running(); } static class Civic implements ICar { public void running() { System.out.println("Civic second day second earth second air"); } } static class Driver implements IDriver { public ICar car; public Driver(ICar car) { this.car = car; } public void drive() { car.running(); } } }
Different from interface passing, Icar is taken as the member variable of Driver and obtained through the constructor.
setter mode transmission
package com.wangscaler.dependencytransfer; /** * @author wangscaler * @date 2021.06.17 17:14 */ public class DependencyTransfer2 { public static void main(String[] args) { Driver driver = new Driver(); driver.setCar(new Civic()); driver.drive(); } interface IDriver { void drive(); } interface ICar { void running(); } static class Civic implements ICar { public void running() { System.out.println("Civic second day second earth second air"); } } static class Driver implements IDriver { public ICar car; public void drive() { car.running(); } public void setCar(ICar car) { this.car = car; } } }
For the member variable Icar, write the Setter method and pass in Icar.
Aside: if you don't recruit black people, my civic is the air every second.
summary
At this point, we have finished our seven principles. The core of the design principles are three points: first, find out the possible changes and extract them. 2, For interface programming, focus on abstraction rather than concrete. 3, Loose coupling. borrow Language Chinese network A sentence: access plus restrictions, functions should be frugal, dependencies are not allowed, interfaces should be added dynamically, parent classes should be abstract, extensions do not change, just learn these concepts, and it is difficult to use them flexibly. Seeing this, you may feel that you have learned something. Maybe you feel that you haven't learned anything and practice your true knowledge. Next, continue to follow my column Design mode Study how design patterns really apply these principles, and you may really understand them.
Design mode – singleton mode
This article mainly explains the singleton patterns in design patterns. Which singleton patterns do you know? Are these suitable for our development? Let's have a look.
Singleton mode
That is, there is only one instance of a class in the whole software system
- Hungry Han formula (static constant)
- Hungry Chinese (static code block)
- Lazy (thread unsafe)
- Lazy (thread safety, synchronization method)
- Lazy (thread safe, synchronous code block)
- Double check lock
- Static inner class
- enumeration
Hungry Han formula (static constant)
package com.wangscaler.singleton; /** * @author wangscaler * @date 2021.06.18 11:18 */ public class Hungryman { public static void main(String[] args) { StatiConst statiConst1 = StatiConst.getInstance(); StatiConst statiConst2 = StatiConst.getInstance(); System.out.println(statiConst2.hashCode()); System.out.println(statiConst1.hashCode()); } static class StatiConst { //After privatization, external cannot be new private StatiConst() { } private final static StatiConst instance = new StatiConst(); public static StatiConst getInstance() { return instance; } } }
Conclusion: attention should be paid to the construction of hungry Chinese style
- 1. Constructor privatize private staticconst() {}
- 2. Class, private final static staticconst instance = new staticconst();
- 3. Expose the static public method public static staticconst getinstance() {return instance;}
- 4. This method will instantiate when the class is loaded, so the global instance object obtained through getInstance will always be one. This method is thread safe.
- If this instance is not used in the project, it will cause a waste of memory.
Hungry Chinese (static code block)
package com.wangscaler.singleton; /** * @author wangscaler * @date 2021.06.18 11:18 */ public class Hungryman1 { public static void main(String[] args) { StatiCodeBlock statiConst1 = StatiCodeBlock.getInstance(); StatiCodeBlock statiConst2 = StatiCodeBlock.getInstance(); System.out.println(statiConst2.hashCode()); System.out.println(statiConst1.hashCode()); } static class StatiCodeBlock { //After privatization, external cannot be new private StatiCodeBlock() { } static { instance = new StatiCodeBlock(); } private static StatiCodeBlock instance; public static StatiCodeBlock getInstance() { return instance; } } }
This is the same way that static constants are created.
Lazy (thread unsafe)
package com.wangscaler.singleton; /** * @author wangscaler * @date 2021.06.18 14:06 */ public class Lazyman { public static void main(String[] args) { Unsafe instance = Unsafe.getInstance(); Unsafe instance2 = Unsafe.getInstance(); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); System.out.println(instance == instance2); } static class Unsafe { private static Unsafe instance; private Unsafe() { } public static Unsafe getInstance() { if (instance == null) { instance = new Unsafe(); } return instance; } } }
In a single thread, this method creates this object only when it is used for the first time. When it has been created, the previously created object will be returned. However, it is possible to initialize multiple instances in multithreading, so this method is thread unsafe. Remember not to use this method in actual development.
Lazy (thread safety, synchronization method)
package com.wangscaler.singleton; /** * @author wangscaler * @date 2021.06.18 14:06 */ public class Lazyman1 { public static void main(String[] args) { Synchronizationmethod instance = Synchronizationmethod.getInstance(); Synchronizationmethod instance2 = Synchronizationmethod.getInstance(); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); System.out.println(instance == instance2); } static class Synchronizationmethod { private static Synchronizationmethod instance; private Synchronizationmethod() { } public static synchronized Synchronizationmethod getInstance() { if (instance == null) { instance = new Synchronizationmethod(); } return instance; } } }
If you change the getInstance method into a synchronous method, each thread will be blocked. You must wait for the last thread to access before continuing to access. The efficiency is too low.
Lazy (thread safe, synchronous code block)
package com.wangscaler.singleton; import sun.misc.JavaAWTAccess; /** * @author wangscaler * @date 2021.06.18 14:06 */ public class Lazyman2 { public static void main(String[] args) { Synchronizationcodeblock instance = Synchronizationcodeblock.getInstance(); Synchronizationcodeblock instance2 = Synchronizationcodeblock.getInstance(); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); System.out.println(instance == instance2); } static class Synchronizationcodeblock { private static Synchronizationcodeblock instance; private Synchronizationcodeblock() { } public static Synchronizationcodeblock getInstance() { if (instance == null) { synchronized (Lazyman2.class) { instance = new Synchronizationcodeblock(); } } return instance; } } }
Putting the synchronization mechanism into the code block is actually the same as the lazy type (thread unsafe). It does not play the role of synchronization, and multiple instances will be generated, so it is impossible to use it.
Double check lock
package com.wangscaler.singleton; public class Doublechecklock { public static void main(String[] args) { Doublecheck doublecheck = Doublecheck.getInstance(); Doublecheck doublecheck1 = Doublecheck.getInstance(); System.out.println(doublecheck.hashCode()); System.out.println(doublecheck1.hashCode()); System.out.println(doublecheck == doublecheck1); } static class Doublecheck { private static volatile Doublecheck instance; private Doublecheck() { } public static Doublecheck getInstance() { if (instance == null) { synchronized (Doublecheck.class) { if (instance == null) { instance = new Doublecheck(); } } } return instance; } } }
At this time, synchronized (Doublecheck.class) can be regarded as a door lock. The first if (instance == null) can be regarded as a checkpoint in front of the door, and the second is a checkpoint behind the door. The name of this double inspection lock is quite accurate. If three threads pass the first checkpoint, when the first thread gets the key to open the door, the remaining two can only wait. When the first thread creates the object through the second checkpoint and flushes it to memory, the remaining threads cannot pass the second checkpoint
You can see that a keyword volatile is used here. This keyword can prohibit instruction rearrangement. This keyword was problematic before Java 5.
Here, instance = new Doublecheck(); There are actually three operations.
1. Allocate memory to instance
2. Call the constructor to initialize the variable
3. Point the object to the memory space allocated for it
These three operations will result in instruction rearrangement, i.e. 1-2-3 / 1-3-2.
- So what is instruction rearrangement?
Instruction rearrangement: in order to improve program efficiency, the processor may optimize the input code. It does not guarantee that the execution sequence of each statement in the program is consistent with that in the code, but it will ensure that the final execution result of the program is consistent with that of the code.
In short, the operation of the code is not necessarily in the coding order, but it can rearrange the instructions that do not depend on according to the data dependency between the instructions, so as to ensure the consistency of the results and improve the efficiency.
If an instruction rearrangement occurs, that is, when 3 is executed first and 2 is executed, a new thread will come in after 3 is executed. At this time, 2 has not been executed, and there will be a problem with the object obtained by the new thread (after 3 is executed, the object at this time is not null).
- Why is this happening?
The Java Memory Model stipulates that all variables are stored in main memory (similar to the physical memory mentioned above), and each thread has its own working memory (similar to the cache mentioned above). The visibility of ordinary shared variables cannot be guaranteed, because when ordinary shared variables are written to main memory after being modified is uncertain. When other threads read them, the original old value may still be in memory, so the visibility cannot be guaranteed.
After using volatile, it will not only prevent instruction rearrangement, but also immediately update the modified value to main memory. When other threads need to read, it will read the new value in memory.
reference material
In the previous section, we talked about some singleton patterns. Next, we will continue to explain the static internal classes and enumerations, as well as the impact of deserialization on the singleton pattern and the singleton pattern in the source code.
Upper section We talked about some singleton patterns. Next, we will continue to explain the static internal classes and enumerations, as well as the singleton patterns in the source code.
Static inner class
package com.wangscaler.singleton; /** * @author wangscaler * @date 2021.06.21 17:42 */ public class Staticinnerclass { public static void main(String[] args) { Staticinnerclass staticinnerclass = Staticinnerclass.getInstance(); Staticinnerclass staticinnerclass1 = Staticinnerclass.getInstance(); System.out.println(staticinnerclass.hashCode()); System.out.println(staticinnerclass1.hashCode()); System.out.println(staticinnerclass == staticinnerclass1); } private Staticinnerclass() { } private static class StaticinnerInstance { private static final Staticinnerclass INSTANCE = new Staticinnerclass(); } public static Staticinnerclass getInstance() { return StaticinnerInstance.INSTANCE; } }
When staticnerclass is loaded, the static internal class staticnerinstance will not be loaded. It will be initialized only when the code calls getInstance. This mode can ensure both thread safety and lazy loading. Therefore, it is also recommended.
enumeration
package com.wangscaler.singleton; /** * @author wangscaler * @date 2021.06.21 17:59 */ public class Enumeration { public static void main(String[] args) { EnumerationSingleton enumerationSingleton = EnumerationSingleton.INSTANCE; EnumerationSingleton enumerationSingleton1 = EnumerationSingleton.INSTANCE; System.out.println(enumerationSingleton.hashCode()); System.out.println(enumerationSingleton1.hashCode()); System.out.println(enumerationSingleton == enumerationSingleton1); } } enum EnumerationSingleton { INSTANCE; }
This method not only avoids the problem of multi-threaded synchronization, but also prevents deserialization and re creation of new objects. It is also recommended. When it comes to deserialization, what impact does deserialization have on the singleton pattern?
Effect of deserialization on singleton pattern
package com.wangscaler.singleton; import java.io.*; /** * @author wangscaler * @date 2021.06.18 11:18 */ public class Hungryman { public static void main(String[] args) throws IOException, ClassNotFoundException { StatiConst statiConst1 = StatiConst.getInstance(); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonDeserialization")); outputStream.writeObject(statiConst1); File file = new File("SingletonDeserialization"); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file)); StatiConst statiConst = (StatiConst) inputStream.readObject(); System.out.println(statiConst.hashCode()); System.out.println(statiConst1.hashCode()); System.out.println(statiConst == statiConst1); } static class StatiConst implements Serializable{ //After privatization, external cannot be new private StatiConst() { } private final static StatiConst instance = new StatiConst(); public static StatiConst getInstance() { return instance; } } }
The result after execution is
false
It can be seen from the above that after the object is written to the file, read from the file and de sequenced into an object, the hash obtained is not the previous object. So how do we make the object after deserialization or the object before it? Just add the readResolve method. When the JVM deserializes and "assembles" a new object from memory, it will automatically call the readResolve method to return the specified object, so as to ensure the singleton rule. Modified code
package com.wangscaler.singleton; import java.io.*; /** * @author wangscaler * @date 2021.06.18 11:18 */ public class Hungryman { public static void main(String[] args) throws IOException, ClassNotFoundException { StatiConst statiConst1 = StatiConst.getInstance(); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonDeserialization")); outputStream.writeObject(statiConst1); File file = new File("SingletonDeserialization"); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file)); StatiConst statiConst = (StatiConst) inputStream.readObject(); System.out.println(statiConst.hashCode()); System.out.println(statiConst1.hashCode()); System.out.println(statiConst == statiConst1); } static class StatiConst implements Serializable{ //After privatization, external cannot be new private StatiConst() { } private final static StatiConst instance = new StatiConst(); public static StatiConst getInstance() { return instance; } private Object readResolve(){ return instance; } } }
results of enforcement
true
It can be seen that after we added this method, we achieved our expected effect. When did the program execute this code? Open the source code and we find
/** if true, invoke readObjectOverride() instead of readObject() */ private final boolean enableOverride; /** * Read an object from the ObjectInputStream. The class of the object, the * signature of the class, and the values of the non-transient and * non-static fields of the class and all of its supertypes are read. * Default deserializing for a class can be overridden using the writeObject * and readObject methods. Objects referenced by this object are read * transitively so that a complete equivalent graph of objects is * reconstructed by readObject. * * <p>The root object is completely restored when all of its fields and the * objects it references are completely restored. At this point the object * validation callbacks are executed in order based on their registered * priorities. The callbacks are registered by objects (in the readObject * special methods) as they are individually restored. * * <p>Exceptions are thrown for problems with the InputStream and for * classes that should not be deserialized. All exceptions are fatal to * the InputStream and leave it in an indeterminate state; it is up to the * caller to ignore or recover the stream state. * * @throws ClassNotFoundException Class of a serialized object cannot be * found. * @throws InvalidClassException Something is wrong with a class used by * serialization. * @throws StreamCorruptedException Control information in the * stream is inconsistent. * @throws OptionalDataException Primitive data was found in the * stream instead of objects. * @throws IOException Any of the usual Input/Output related exceptions. */ public final Object readObject() throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride(); } // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false); handles.markDependency(outerHandle, passHandle); ClassNotFoundException ex = handles.lookupException(passHandle); if (ex != null) { throw ex; } if (depth == 0) { vlist.doCallbacks(); } return obj; } finally { passHandle = outerHandle; if (closed && depth == 0) { clear(); } } } /** * Underlying readObject implementation. */ private Object readObject0(boolean unshared) throws IOException { boolean oldMode = bin.getBlockDataMode(); if (oldMode) { int remain = bin.currentBlockRemaining(); if (remain > 0) { throw new OptionalDataException(remain); } else if (defaultDataEnd) { /* * Fix for 4360508: stream is currently at the end of a field * value block written via default serialization; since there * is no terminating TC_ENDBLOCKDATA tag, simulate * end-of-custom-data behavior explicitly. */ throw new OptionalDataException(true); } bin.setBlockDataMode(false); } byte tc; while ((tc = bin.peekByte()) == TC_RESET) { bin.readByte(); handleReset(); } depth++; totalObjectRefs++; try { switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); case TC_EXCEPTION: IOException ex = readFatalException(); throw new WriteAbortedException("writing aborted", ex); case TC_BLOCKDATA: case TC_BLOCKDATALONG: if (oldMode) { bin.setBlockDataMode(true); bin.peek(); // force header read throw new OptionalDataException( bin.currentBlockRemaining()); } else { throw new StreamCorruptedException( "unexpected block data"); } case TC_ENDBLOCKDATA: if (oldMode) { throw new OptionalDataException(true); } else { throw new StreamCorruptedException( "unexpected end of block data"); } default: throw new StreamCorruptedException( String.format("invalid type code: %02X", tc)); } } finally { depth--; bin.setBlockDataMode(oldMode); } } /** * Reads and returns "ordinary" (i.e., not a String, Class, * ObjectStreamClass, array, or enum constant) object, or null if object's * class is unresolvable (in which case a ClassNotFoundException will be * associated with object's handle). Sets passHandle to object's assigned * handle. */ private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } } return obj; }
In our program, we call inputStream.. After readobject(), he will first judge whether there is readObjectOverride(). If so, it will replace readObject, which is not available here. Therefore, the program will execute Object obj = readObject0(false); After executing this code and entering readObject0, we will go to case TC because we are objects_ The readOrdinaryObject(unshared) in object opens the source code of this method. We find that there is such a code obj = desc.isinstantable()? desc.newInstance() : null; First judge whether the object can be instantiated. If so, instantiate it. Then
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())
Judge whether we have written the ReadResolve method through desc.hasReadResolveMethod(). If we have, call desc.invokereaderresolve (obj); To call the ReadResolve method we wrote. Open the source code of invokereaderresolve as follows
/** class-defined readResolve method, or null if none */ private Method readResolveMethod; /** * Invokes the readResolve method of the represented serializable class and * returns the result. Throws UnsupportedOperationException if this class * descriptor is not associated with a class, or if the class is * non-serializable or does not define readResolve. */ Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException { requireInitialized(); if (readResolveMethod != null) { try { return readResolveMethod.invoke(obj, (Object[]) null); } catch (InvocationTargetException ex) { Throwable th = ex.getTargetException(); if (th instanceof ObjectStreamException) { throw (ObjectStreamException) th; } else { throwMiscException(th); throw new InternalError(th); // never reached } } catch (IllegalAccessException ex) { // should not occur, as access checks have been suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }
After calling the method we wrote, he will judge whether the object we returned is the same as the object he just generated
if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); }
If it is different, we will assign the returned object to the object generated before it, so whether we deserialize the object or the previous object. So why can enumerating types get this object without writing this method
/** * Reads in and returns enum constant, or null if enum type is * unresolvable. Sets passHandle to enum constant's assigned handle. */ private Enum<?> readEnum(boolean unshared) throws IOException { if (bin.readByte() != TC_ENUM) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); if (!desc.isEnum()) { throw new InvalidClassException("non-enum class: " + desc); } int enumHandle = handles.assign(unshared ? unsharedMarker : null); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(enumHandle, resolveEx); } String name = readString(false); Enum<?> result = null; Class<?> cl = desc.forClass(); if (cl != null) { try { @SuppressWarnings("unchecked") Enum<?> en = Enum.valueOf((Class)cl, name); result = en; } catch (IllegalArgumentException ex) { throw (IOException) new InvalidObjectException( "enum constant " + name + " does not exist in " + cl).initCause(ex); } if (!unshared) { handles.setObject(enumHandle, result); } } handles.finish(enumHandle); passHandle = enumHandle; return result; }
We can see enum in the source code valueOf((Class)cl, name);, Take out the object directly according to the name, and then result = en; Assigned to the previous object.
Singleton mode in JDK
Take the Runtime in JDK as an example
public class Runtime { private static Runtime getRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} }
You can see that the source code of our Runtime is the hungry Chinese style in the singleton mode.
public class Desktop { /** * Represents an action type. Each platform supports a different * set of actions. You may use the {@link Desktop#isSupported} * method to determine if the given action is supported by the * current platform. * @see java.awt.Desktop#isSupported(java.awt.Desktop.Action) * @since 1.6 */ public static enum Action { /** * Represents an "open" action. * @see Desktop#open(java.io.File) */ OPEN, /** * Represents an "edit" action. * @see Desktop#edit(java.io.File) */ EDIT, /** * Represents a "print" action. * @see Desktop#print(java.io.File) */ PRINT, /** * Represents a "mail" action. * @see Desktop#mail() * @see Desktop#mail(java.net.URI) */ MAIL, /** * Represents a "browse" action. * @see Desktop#browse(java.net.URI) */ BROWSE }; private DesktopPeer peer; /** * Suppresses default constructor for noninstantiability. */ private Desktop() { peer = Toolkit.getDefaultToolkit().createDesktopPeer(this); } /** * Returns the <code>Desktop</code> instance of the current * browser context. On some platforms the Desktop API may not be * supported; use the {@link #isDesktopSupported} method to * determine if the current desktop is supported. * @return the Desktop instance of the current browser context * @throws HeadlessException if {@link * GraphicsEnvironment#isHeadless()} returns {@code true} * @throws UnsupportedOperationException if this class is not * supported on the current platform * @see #isDesktopSupported() * @see java.awt.GraphicsEnvironment#isHeadless */ public static synchronized Desktop getDesktop(){ if (GraphicsEnvironment.isHeadless()) throw new HeadlessException(); if (!Desktop.isDesktopSupported()) { throw new UnsupportedOperationException("Desktop API is not " + "supported on the current platform"); } sun.awt.AppContext context = sun.awt.AppContext.getAppContext(); Desktop desktop = (Desktop)context.get(Desktop.class); if (desktop == null) { desktop = new Desktop(); context.put(Desktop.class, desktop); } return desktop; } }
The Desktop here is the singleton mode of the enumeration type used.
summary
To sum up, in the singleton mode, you can use
-
Hungry Han style (single thread, which will cause a waste of memory)
-
Double check lock
-
Static inner class
-
enumeration
Usage: it is necessary to frequently create and destroy objects or create objects that take a long time and have a lot of resources (such as session factories, tool classes...), which may be triggered frequently during the development process of the Runtime, greatly reducing the occupation of resources.
Note: to use the singleton mode, you must use the provided get method instead of the new object. For example, use the getRuntime method to obtain the Runtime object in the Runtime.
Benefits: save resources and improve performance.
Disadvantages: if it is not used for a long time, it will be recycled and the status data will be lost; If too many programs use this object, it will lead to overflow and is not suitable for frequently changing objects
reference material
Design mode – factory mode
This chapter will introduce design patterns, including simple factory pattern, factory method pattern and abstract factory pattern, and when to use factory pattern.
Simple factory mode
For example, we have a restaurant. The chef of the restaurant has the following steps: 1. Select ingredients 2. Cut vegetables 3. Stir fry vegetables 4. Load plates 5. Serve
At this time, no matter what dishes are 2, 3, 4 and 5, they are the same. Let's write this
Cook
package com.wangscaler.factory; /** * @author wangscaler * @date 2021.06.22 16:17 */ public abstract class Cook { protected String name; public abstract void prepare(); public void cut() { System.out.println(name + "The material has been cut"); } public void fry() { System.out.println(name + "Fired"); } public void dish() { System.out.println(name + "It's on the plate"); } public void serve() { System.out.println(name + "It's on the table"); } public void setName(String name) { this.name = name; } }
FishFlavoredPork
package com.wangscaler.factory; /** * @author wangscaler * @date 2021.06.22 16:17 */ public class FishFlavoredPork extends Cook { @Override public void prepare() { System.out.println("Carrots, shredded meat, sweet sauce and other materials are ready"); } }
KungPaoChicken
package com.wangscaler.factory; /** * @author wangscaler * @date 2021.06.22 16:17 */ public class KungPaoChicken extends Cook { @Override public void prepare() { System.out.println("Diced chicken, cucumber, peanuts and other materials are ready"); } }
Order
package com.wangscaler.factory; import java.io.BufferedReader; import java.io.InputStreamReader; /** * @author wangscaler * @date 2021.06.22 16:17 */ public class Order { public Order() { Cook cook = null; String type; do { type = geType(); if (type.equals(Menu.FISHFLAVOREDPORK.getName())) { cook = new FishFlavoredPork(); cook.setName("Yu-Shiang Shredded Pork"); } else if (type.equals(Menu.KUNGPAOCHICKEN.getName())) { cook = new KungPaoChicken(); cook.setName("Kung Pao Chicken"); } else if (type.equals(Menu.EOF.getName())) { break; } else { break; } cook.prepare(); cook.cut(); cook.fry(); cook.dish(); cook.serve(); } while (true); } public enum Menu { /** * Yu-Shiang Shredded Pork */ FISHFLAVOREDPORK("fishflavoredpork"), /** * Kung Pao Chicken */ KUNGPAOCHICKEN("kungpaochicken"), /** * End identification */ EOF("EOF"); private String name; Menu(String name) { this.name = name; } public String getName() { return name; } } private String geType() { try { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Please enter your order,with EOF For the end"); String str = bufferedReader.readLine(); return str; } catch (Exception e) { e.printStackTrace(); return ""; } } }
main
package com.wangscaler.factory; /** * @author wangscaler * @date 2021.06.22 16:17 */ public class SimpleFactory { public static void main(String[] args) { Order order =new Order(); } }
At this time, the operation results show that everything is normal
Please enter your order,with EOF For the end kungpaochicken Diced chicken, cucumber, peanuts and other materials are ready The material of Gongbao diced chicken has been cut Kung pao chicken has been fried Kung Pao diced chicken is on the plate Kung Pao diced chicken is on the table Please enter your order,with EOF For the end fishflavoredpork Carrots, shredded meat, sweet sauce and other materials are ready The material of fish flavored shredded pork has been cut Fish flavored shredded pork has been fried Fish flavored shredded meat was put on the plate Fish flavored shredded pork is on the table Please enter your order,with EOF For the end EOF
However, when we add new recipes to the menu, we should not only add new classes, but also modify the Order class. If there are many restaurants, we may have to write many orders. This change is not only troublesome, but also violates the opening and closing principles of our design principles, such as opening and closing extensions and closing modifications.
Modify Order
package com.wangscaler.factory; import java.io.BufferedReader; import java.io.InputStreamReader; /** * @author wangscaler * @date 2021.06.22 16:17 */ public class Order { // public Order() { // Cook cook = null; // String type; // do { // type = geType(); // if (type.equals(Menu.FISHFLAVOREDPORK.getName())) { // cook = new FishFlavoredPork(); // cook.setName("fish flavored shredded pork"); // } else if (type.equals(Menu.KUNGPAOCHICKEN.getName())) { // cook = new KungPaoChicken(); // cook.setName("Kung Pao diced chicken"); // } else if (type.equals(Menu.EOF.getName())) { // break; // } else { // break; // } // cook.prepare(); // cook.cut(); // cook.fry(); // cook.dish(); // cook.serve(); // } while (true); // } SimpleFactory simpleFactory; Cook cook = null; public Order(SimpleFactory simpleFactory) { setFactory(simpleFactory); } public void setFactory(SimpleFactory simpleFactory) { String type = ""; this.simpleFactory = simpleFactory; do { type = geType(); cook = this.simpleFactory.createCook(type); if (cook != null) { cook.prepare(); cook.cut(); cook.fry(); cook.dish(); cook.serve(); } else { break; } } while (true); } private String geType() { try { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Please enter your order,with EOF For the end"); String str = bufferedReader.readLine(); return str; } catch (Exception e) { e.printStackTrace(); return ""; } } }
Modify SimpleFactory
package com.wangscaler.factory; /** * @author wangscaler * @date 2021.06.22 16:17 */ public class SimpleFactory { public Cook createCook(String type) { Cook cook = null; if (type.equals(SimpleFactory.Menu.FISHFLAVOREDPORK.getName())) { cook = new FishFlavoredPork(); cook.setName("Yu-Shiang Shredded Pork"); } else if (type.equals(SimpleFactory.Menu.KUNGPAOCHICKEN.getName())) { cook = new KungPaoChicken(); cook.setName("Kung Pao Chicken"); } return cook; } public enum Menu { /** * Yu-Shiang Shredded Pork */ FISHFLAVOREDPORK("fishflavoredpork"), /** * Kung Pao Chicken */ KUNGPAOCHICKEN("kungpaochicken"), /** * End identification */ EOF("EOF"); private String name; Menu(String name) { this.name = name; } public String getName() { return name; } } public static void main(String[] args) { new Order(new SimpleFactory()); } }
When we add a new menu, we only need to add a new menu class and modify the factory, which has no impact on Order. Simple factory mode is also called static factory mode because our code can be modified to
Add static to the createbook method of SimpleFactory, and then modify the Order
package com.wangscaler.factory; import java.io.BufferedReader; import java.io.InputStreamReader; /** * @author wangscaler * @date 2021.06.22 16:17 */ public class Order1 { Cook cook = null; String type = ""; public Order1() { do { type = geType(); cook = SimpleFactory.createCook(type); if (cook != null) { cook.prepare(); cook.cut(); cook.fry(); cook.dish(); cook.serve(); } else { break; } } while (true); } private String geType() { try { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Please enter your order,with EOF For the end"); String str = bufferedReader.readLine(); return str; } catch (Exception e) { e.printStackTrace(); return ""; } } }
Since all the dishes are produced by SimpleFactory, if the factory has problems, all the dishes produced by it will have problems. The following pattern delays the instantiation of objects to subclasses.
Factory method model
For example, we not only have dishes, but also add flavor, such as spicy and non spicy dishes (just understand the meaning, don't worry about whether you can eat or not, ha ha), that is, spicy fish flavored shredded meat and non spicy fish flavored shredded meat.
The above Cook is not modified. The fish flavored shredded meat is changed into spicy fish flavored pork and not spicy fish flavored pork. The content is the same as before. Then we modified the Order and added two factories: Spicy factory and not spicy factory. The code is as follows
Order
package com.wangscaler.factory.factorymethod; import java.io.BufferedReader; import java.io.InputStreamReader; /** * @author wangscaler * @date 2021.06.22 16:17 */ public abstract class Order { Cook cook = null; String type = ""; abstract Cook createCook(String type); public Order() { do { type = geType(); cook = createCook(type); if (cook != null) { cook.prepare(); cook.cut(); cook.fry(); cook.dish(); cook.serve(); } else { break; } } while (true); } private String geType() { try { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Please enter your order,with EOF For the end"); String str = bufferedReader.readLine(); return str; } catch (Exception e) { e.printStackTrace(); return ""; } } }
SpicyFactory
package com.wangscaler.factory.factorymethod; import com.wangscaler.factory.simplefactory.SimpleFactory; /** * @author wangscaler * @date 2021.06.23 10:10 */ public class SpicyFactory extends Order { @Override Cook createCook(String type) { Cook cook = null; if (type.equals(SimpleFactory.Menu.FISHFLAVOREDPORK.getName())) { cook = new SpicyFishFlavoredPork(); cook.setName("Spicy fish flavored shredded pork"); } else if (type.equals(SimpleFactory.Menu.KUNGPAOCHICKEN.getName())) { cook = new SpicyKungPaoChicken(); cook.setName("Spicy Kung Pao diced chicken"); } return cook; } public enum Menu { /** * Yu-Shiang Shredded Pork */ FISHFLAVOREDPORK("fishflavoredpork"), /** * Kung Pao Chicken */ KUNGPAOCHICKEN("kungpaochicken"), /** * End identification */ EOF("EOF"); private String name; Menu(String name) { this.name = name; } public String getName() { return name; } } }
NotSpicyFactory
package com.wangscaler.factory.factorymethod; import com.wangscaler.factory.simplefactory.SimpleFactory; /** * @author wangscaler * @date 2021.06.23 10:11 */ public class NotSpicyFactory extends Order { @Override Cook createCook(String type) { Cook cook = null; if (type.equals(SimpleFactory.Menu.FISHFLAVOREDPORK.getName())) { cook = new NotSpicyFishFlavoredPork(); cook.setName("Yu-Shiang Shredded Pork"); } else if (type.equals(SimpleFactory.Menu.KUNGPAOCHICKEN.getName())) { cook = new NotSpicyKungPaoChicken(); cook.setName("Kung Pao Chicken"); } return cook; } public enum Menu { /** * Yu-Shiang Shredded Pork */ FISHFLAVOREDPORK("fishflavoredpork"), /** * Kung Pao Chicken */ KUNGPAOCHICKEN("kungpaochicken"), /** * End identification */ EOF("EOF"); private String name; Menu(String name) { this.name = name; } public String getName() { return name; } } }
We can see that we have added the abstract method createbook in Order and handed its implementation to the subclasses NotSpicyFactory and SpicyFactory.
Abstract factory pattern
Abstract factory pattern is the combination of simple factory pattern and factory method pattern. In popular terms, abstract factories are factory families that produce different grades of products, that is, factories that can produce both spicy and non spicy factories.
Keep Cook, notspicyfishflashedpork and spicyfishflashedpork in the factory method mode unchanged, and add an abstract factory AbsFactory
package com.wangscaler.factory.abstractfactory; /** * @author wangscaler * @date 2021.06.23 11:00 */ public interface AbsFactory { public Cook createCook(String type); }
Provide interfaces for spicy and non spicy plants
SpicyFactory
package com.wangscaler.factory.abstractfactory; /** * @author wangscaler * @date 2021.06.23 10:10 */ public class SpicyFactory implements AbsFactory { @Override public Cook createCook(String type) { Cook cook = null; if (type.equals(SpicyFactory.Menu.FISHFLAVOREDPORK.getName())) { cook = new SpicyFishFlavoredPork(); cook.setName("Spicy fish flavored shredded pork"); } else if (type.equals(SpicyFactory.Menu.KUNGPAOCHICKEN.getName())) { cook = new SpicyKungPaoChicken(); cook.setName("Spicy Kung Pao diced chicken"); } return cook; } public enum Menu { /** * Yu-Shiang Shredded Pork */ FISHFLAVOREDPORK("fishflavoredpork"), /** * Kung Pao Chicken */ KUNGPAOCHICKEN("kungpaochicken"), /** * End identification */ EOF("EOF"); private String name; Menu(String name) { this.name = name; } public String getName() { return name; } } }
NotSpicyFactory
package com.wangscaler.factory.abstractfactory; /** * @author wangscaler * @date 2021.06.23 10:10 */ public class NotSpicyFactory implements AbsFactory { @Override public Cook createCook(String type) { Cook cook = null; if (type.equals(NotSpicyFactory.Menu.FISHFLAVOREDPORK.getName())) { cook = new NotSpicyFishFlavoredPork(); cook.setName("Yu-Shiang Shredded Pork"); } else if (type.equals(NotSpicyFactory.Menu.KUNGPAOCHICKEN.getName())) { cook = new NotSpicyKungPaoChicken(); cook.setName("Kung Pao Chicken"); } return cook; } public enum Menu { /** * Yu-Shiang Shredded Pork */ FISHFLAVOREDPORK("fishflavoredpork"), /** * Kung Pao Chicken */ KUNGPAOCHICKEN("kungpaochicken"), /** * End identification */ EOF("EOF"); private String name; Menu(String name) { this.name = name; } public String getName() { return name; } } }
At this point, our Order only needs to use the abstract factory
package com.wangscaler.factory.abstractfactory; import java.io.BufferedReader; import java.io.InputStreamReader; /** * @author wangscaler * @date 2021.06.22 16:17 */ public class Order { AbsFactory factory; private void setFactory(AbsFactory factory) { Cook cook = null; String type = ""; this.factory = factory; do { type = geType(); cook = factory.createCook(type); if (cook != null) { cook.prepare(); cook.cut(); cook.fry(); cook.dish(); cook.serve(); } else { break; } } while (true); } public Order(AbsFactory factory) { setFactory(factory); } private String geType() { try { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Please enter your order,with EOF For the end"); String str = bufferedReader.readLine(); return str; } catch (Exception e) { e.printStackTrace(); return ""; } } }
main function
package com.wangscaler.factory.abstractfactory; /** * @author wangscaler * @date 2021.06.23 11:17 */ public class AbstractFactory { public static void main(String[] args) { new Order(new SpicyFactory()); } }
This method has good scalability. For example, we only need to add a slight spicy factory to realize the createbook method of the abstract factory and the dishes with slight spicy taste.
Factory pattern in source code
Calendar in JDK
Calendar calendar=Calendar.getInstance(); We open the getInstance method of calendar and find
/** * Gets a calendar using the default time zone and locale. The * <code>Calendar</code> returned is based on the current time * in the default time zone with the default * {@link Locale.Category#FORMAT FORMAT} locale. * * @return a Calendar. */ public static Calendar getInstance() { return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT)); } /** * Gets a calendar using the specified time zone and default locale. * The <code>Calendar</code> returned is based on the current time * in the given time zone with the default * {@link Locale.Category#FORMAT FORMAT} locale. * * @param zone the time zone to use * @return a Calendar. */ public static Calendar getInstance(TimeZone zone) { return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT)); } /** * Gets a calendar using the default time zone and specified locale. * The <code>Calendar</code> returned is based on the current time * in the default time zone with the given locale. * * @param aLocale the locale for the week data * @return a Calendar. */ public static Calendar getInstance(Locale aLocale) { return createCalendar(TimeZone.getDefault(), aLocale); } /** * Gets a calendar with the specified time zone and locale. * The <code>Calendar</code> returned is based on the current time * in the given time zone with the given locale. * * @param zone the time zone to use * @param aLocale the locale for the week data * @return a Calendar. */ public static Calendar getInstance(TimeZone zone, Locale aLocale) { return createCalendar(zone, aLocale); } private static Calendar createCalendar(TimeZone zone, Locale aLocale) { CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale) .getCalendarProvider(); if (provider != null) { try { return provider.getInstance(zone, aLocale); } catch (IllegalArgumentException iae) { // fall back to the default instantiation } } Calendar cal = null; if (aLocale.hasExtensions()) { String caltype = aLocale.getUnicodeLocaleType("ca"); if (caltype != null) { switch (caltype) { case "buddhist": cal = new BuddhistCalendar(zone, aLocale); break; case "japanese": cal = new JapaneseImperialCalendar(zone, aLocale); break; case "gregory": cal = new GregorianCalendar(zone, aLocale); break; } } } if (cal == null) { // If no known calendar type is explicitly specified, // perform the traditional way to create a Calendar: // create a BuddhistCalendar for th_TH locale, // a JapaneseImperialCalendar for ja_JP_JP locale, or // a GregorianCalendar for any other locales. // NOTE: The language, country and variant strings are interned. if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") { cal = new BuddhistCalendar(zone, aLocale); } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") { cal = new JapaneseImperialCalendar(zone, aLocale); } else { cal = new GregorianCalendar(zone, aLocale); } } return cal; }
In fact, it calls createCalendar, and in createCalendar, we find that
Calendar cal = null;
if (aLocale.hasExtensions()) { String caltype = aLocale.getUnicodeLocaleType("ca"); if (caltype != null) { switch (caltype) { case "buddhist": cal = new BuddhistCalendar(zone, aLocale); break; case "japanese": cal = new JapaneseImperialCalendar(zone, aLocale); break; case "gregory": cal = new GregorianCalendar(zone, aLocale); break; } } } if (cal == null) { // If no known calendar type is explicitly specified, // perform the traditional way to create a Calendar: // create a BuddhistCalendar for th_TH locale, // a JapaneseImperialCalendar for ja_JP_JP locale, or // a GregorianCalendar for any other locales. // NOTE: The language, country and variant strings are interned. if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") { cal = new BuddhistCalendar(zone, aLocale); } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") { cal = new JapaneseImperialCalendar(zone, aLocale); } else { cal = new GregorianCalendar(zone, aLocale); } } return cal;
It's the simple factory model we're talking about
summary
Design patterns must abide by design principles: rely on abstraction rather than concrete, open to extension and close to modification
Four concepts of factory mode:
- Abstract product (Cook): it is the parent of all concrete products.
- Specific product (KungPaoChicken): it inherits the parent class of Cook and is also an object instance produced by the factory.
- Abstract factory: provides an interface to create objects for subclasses to implement.
- Specific factory (SpicyFactory): it implements the interface of abstract factory and production object instance.
The above three design modes can be used flexibly according to business
- Simple factory mode
- Fewer objects need to be created
- The client does not need to focus on the process of object creation
- Factory method model
- I don't know the class of the object it needs
- Specify which object to create through its subclasses
- Delegate the task of creating objects to one of multiple factory subclasses
- Abstract factory pattern
- One or more groups of objects are required to complete a function
- Objects are not added frequently
- Do not know the class of the required object
reference material
Design pattern – prototype pattern
Prototype pattern is actually a kind of cloning. Specify the type of objects to be created with prototype instances, and create new objects by cloning these prototypes. Prototype patterns have shallow and deep copies. Let's see how to implement prototype patterns.
Prototype mode
Prototype pattern is actually a kind of cloning. Specify the type of objects to be created with prototype instances, and create new objects by cloning these prototypes.
At first, we had a sheep named Dolly, the color was white and the type was sheep. Later, we were rich and needed to introduce more sheep with the same attributes as dolly. How do we write?
Sheep
package com.wangscaler.prototype; /** * @author wangscaler * @date 2021.06.23 17:04 */ public class Sheep { private String name; private String color; private String type; public Sheep(String name, String color, String type) { this.name = name; this.color = color; this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getType() { return type; } public void setType(String type) { this.type = type; } }
main
package com.wangscaler.prototype; /** * @author wangscaler * @date 2021.06.23 17:04 */ public class Prototype { public static void main(String[] args) { Sheep sheep = new Sheep("Dolly", "white", "sheep"); Sheep sheep1 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType()); Sheep sheep2 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType()); Sheep sheep3 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType()); Sheep sheep4 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType()); Sheep sheep5 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType()); Sheep sheep6 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getType()); } }
Although we have cloned the same sheep, we not only re initialize the Object every time, but also re obtain the properties of the original Object. If the Object is complex, it will greatly affect our development efficiency. We found that the Object class has a method called clone, so can we achieve our goal by implementing the clone of the Cloneavle interface?
Sheep1
package com.wangscaler.prototype; /** * @author wangscaler * @date 2021.06.23 17:04 */ public class Sheep1 implements Cloneable { private String name; private String color; private String type; public Sheep1(String name, String color, String type) { this.name = name; this.color = color; this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getType() { return type; } public void setType(String type) { this.type = type; } @Override protected Object clone() throws CloneNotSupportedException { Sheep1 sheep = null; sheep = (Sheep1) super.clone(); return sheep; } }
main
package com.wangscaler.prototype; /** * @author wangscaler * @date 2021.06.23 17:04 */ public class Prototype { public static void main(String[] args) throws CloneNotSupportedException { Sheep1 sheep = new Sheep1("Dolly", "white", "sheep"); Sheep1 sheep1 = (Sheep1) sheep.clone(); Sheep1 sheep2 = (Sheep1) sheep.clone(); Sheep1 sheep3 = (Sheep1) sheep.clone(); Sheep1 sheep4 = (Sheep1) sheep.clone(); Sheep1 sheep5 = (Sheep1) sheep.clone(); Sheep1 sheep6 = (Sheep1) sheep.clone(); } }
We also copied sheep with the same attributes.
Shallow copy
- For member variables of basic types, values are passed
- For variables of reference type, the reference will be passed, that is, the memory address will be passed. At this time, the objects before and after copying are actually the same memory address. If you modify one of them, the other will also be modified (see my reasons) JAVA error prone point 1)
- The clone method in the above cloning sheep is shallow copy
Deep copy
- Basic types produce value passing
- The reference type variable will apply for storage space and copy the object referenced by the reference type variable
Implementation mode
- Override clone method
- Object serialization
Method of overriding clone
package com.wangscaler.prototype; import java.io.Serializable; /** * @author wangscaler * @date 2021.06.24 10:14 */ public class DeepClone implements Serializable,Cloneable { private static final long serialVersionID = 1L; private String name; private String aclass; public DeepClone(String name, String aclass) { this.name = name; this.aclass = aclass; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
DeeProtoTypeClone
package com.wangscaler.prototype; import java.io.Serializable; /** * @author wangscaler * @date 2021.06.24 10:14 */ public class DeeProtoTypeClone implements Serializable, Cloneable { private String name; private DeepClone deepClone; public DeeProtoTypeClone() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public DeepClone getDeepClone() { return deepClone; } public void setDeepClone(DeepClone deepClone) { this.deepClone = deepClone; } @Override public String toString() { return "DeeProtoTypeClone{" + " + name + '\'' + ", deepClone=" + deepClone + '}'; } @Override protected Object clone() throws CloneNotSupportedException { Object deep = null; deep = super.clone(); DeeProtoTypeClone deeProtoTypeClone = (DeeProtoTypeClone) deep; deeProtoTypeClone.deepClone = (DeepClone) deepClone.clone(); return deeProtoTypeClone; } }
main
package com.wangscaler.prototype; /** * @author wangscaler * @date 2021.06.23 17:04 */ public class Prototype { public static void main(String[] args) throws CloneNotSupportedException { DeeProtoTypeClone deeProtoTypeClone = new DeeProtoTypeClone(); deeProtoTypeClone.setName("Qianlong"); DeepClone deepClone = new DeepClone("Ji Xiaolan", "Honest and upright officials"); deeProtoTypeClone.setDeepClone(deepClone); DeeProtoTypeClone deeProtoTypeClone1 = (DeeProtoTypeClone) deeProtoTypeClone.clone(); DeeProtoTypeClone deeProtoTypeClone2 = (DeeProtoTypeClone) deeProtoTypeClone.clone(); System.out.println(deeProtoTypeClone.toString()); System.out.println(deeProtoTypeClone1.toString()); System.out.println(deeProtoTypeClone2.toString()); } }
results of enforcement
DeeProtoTypeClone{name='Qianlong', deepClone=com.wangscaler.prototype.DeepClone@1540e19d} DeeProtoTypeClone{name='Qianlong', deepClone=com.wangscaler.prototype.DeepClone@677327b6} DeeProtoTypeClone{name='Qianlong', deepClone=com.wangscaler.prototype.DeepClone@14ae5a5}
We can find that different from shallow copy, we successfully copied several deep clone objects
Object serialization
DeeProtoTypeClone adds a serialized method
package com.wangscaler.prototype; import java.io.*; /** * @author wangscaler * @date 2021.06.24 10:14 */ public class DeeProtoTypeClone implements Serializable, Cloneable { private String name; private DeepClone deepClone; public DeeProtoTypeClone() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public DeepClone getDeepClone() { return deepClone; } public void setDeepClone(DeepClone deepClone) { this.deepClone = deepClone; } @Override public String toString() { return "DeeProtoTypeClone{" + " + name + '\'' + ", deepClone=" + deepClone + '}'; } // @Override // protected Object clone() throws CloneNotSupportedException { // Object deep = null; // deep = super.clone(); // DeeProtoTypeClone deeProtoTypeClone = (DeeProtoTypeClone) deep; // deeProtoTypeClone.deepClone = (DeepClone) deepClone.clone(); // return deeProtoTypeClone; // } public Object deepClone() { ObjectInputStream objectInputStream = null; ObjectOutputStream objectOutputStream = null; ByteArrayInputStream byteArrayInputStream = null; ByteArrayOutputStream byteArrayOutputStream = null; try { byteArrayOutputStream = new ByteArrayOutputStream(); objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(this); byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); objectInputStream = new ObjectInputStream(byteArrayInputStream); DeeProtoTypeClone deeProtoTypeClone = (DeeProtoTypeClone) objectInputStream.readObject(); return deeProtoTypeClone; } catch (Exception e) { e.printStackTrace(); return null; } finally { try { objectInputStream.close(); objectOutputStream.close(); byteArrayInputStream.close(); byteArrayOutputStream.close(); } catch (Exception e1) { e1.printStackTrace(); } } } }
It can also be realized. The second method is recommended.
Prototype pattern in source code
bean creation in spring
<bean id="id01" class="com.wangscaler.bean.Person" scope="prototype"/>
We designated him as the prototype pattern. When we get the bean object through getBean(), we can find that the properties of the created object are the same.
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); Object bean =applicationContext.getBean("id01"); Object bean1 =applicationContext.getBean("id01"); System.out.println("bean"); System.out.println("bean1");
When we click in the source code of getBean, we find that
public Object getBean(String name) throws BeansException { this.assertBeanFactoryActive(); return this.getBeanFactory().getBean(name); }
The getBeanFactory method is called here. Click in to find out
private DefaultListableBeanFactory beanFactory; public final ConfigurableListableBeanFactory getBeanFactory() { synchronized(this.beanFactoryMonitor) { if (this.beanFactory == null) { throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext"); } else { return this.beanFactory; } } }
synchronized synchronization is used here to ensure that threads can safely return to the factory.
We found it in the getBean method in the previous step
public Object getBean(String name) throws BeansException { return this.doGetdBean(name, (Class)null, (Object[])null, false); }
doGetdBean is called here. Continue to enter
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = this.transformedBeanName(name); Object sharedInstance = this.getSingleton(beanName); if (mbd.isSingleton()) { sharedInstance = this.getSingleton(beanName, () -> { try { return this.createBean(beanName, mbd, args); } catch (BeansException var5) { this.destroySingleton(beanName); throw var5; } }); bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { var11 = null; Object prototypeInstance; try { this.beforePrototypeCreation(beanName); prototypeInstance = this.createBean(beanName, mbd, args); } finally { this.afterPrototypeCreation(beanName); } bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); }
Here, we found that we first judge whether it is the singleton mode if (MBD. Isprototype()) and then judge whether else if (mbd.isPrototype()) is the prototype mode. If so, we will call createBean to create the prototype instance.
summary
Using prototype mode can improve our efficiency without initializing the object, and when the properties of the object change, the changed object can also be copied directly by cloning. When the object is complex or we do not know the parameters of the runtime object, the prototype pattern is recommended.
Design pattern - builder pattern
Builder pattern
For example, we build houses. The process of building houses includes piling, building walls and capping. The types of houses include bungalows, buildings and villas
Our code is written like this
AbsHouse
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.24 13:56 */ public abstract class AbsHouse { public abstract void layingFoundation(); public abstract void buildWall(); public abstract void sealRoof(); public void build() { layingFoundation(); buildWall(); sealRoof(); } }
Bungalow
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.24 13:56 */ public class Bungalow extends AbsHouse { @Override public void layingFoundation() { System.out.println("Bungalow Foundation"); } @Override public void buildWall() { System.out.println("Bungalow wall"); } @Override public void sealRoof() { System.out.println("Bungalow capping"); } }
main
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.24 13:56 */ public class Builder { public static void main(String[] args) { Bungalow bungalow = new Bungalow(); bungalow.build(); } }
In this way, the coupling is strong, and the house construction process is encapsulated together, so it is necessary to decouple the house and the house construction process
Product role House
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.23 14:46 */ public class House { private String foundation; private String wall; private String roof; public String getFoundation() { return foundation; } public void setFoundation(String foundation) { this.foundation = foundation; } public String getWall() { return wall; } public void setWall(String wall) { this.wall = wall; } public String getRoof() { return roof; } public void setRoof(String roof) { this.roof = roof; } }
Abstract builder HouseBuilder
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.23 14:46 */ public abstract class HouseBuilder { protected House house = new House(); public abstract void layingFoundation(); public abstract void buildWall(); public abstract void sealRoof(); public House buildHouse() { return house; } }
Specific Builder: Bungalow
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.24 13:56 */ public class Bungalow extends HouseBuilder { @Override public void layingFoundation() { System.out.println("Bungalow Foundation"); } @Override public void buildWall() { System.out.println("Bungalow wall"); } @Override public void sealRoof() { System.out.println("Bungalow capping"); } }
And another specific builder, Villa
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.24 14:49 */ public class Villa extends HouseBuilder { @Override public void layingFoundation() { System.out.println("Villa Foundation"); } @Override public void buildWall() { System.out.println("Villa wall"); } @Override public void sealRoof() { System.out.println("Villa capping"); } }
Commander
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.23 14:46 */ public class HouseDirector { HouseBuilder houseBuilder = null; public HouseDirector(HouseBuilder houseBuilder) { this.houseBuilder = houseBuilder; } public void setHouseBuilder(HouseBuilder houseBuilder) { this.houseBuilder = houseBuilder; } public House constructHouse() { houseBuilder.layingFoundation(); houseBuilder.buildWall(); houseBuilder.sealRoof(); return houseBuilder.buildHouse(); } }
main
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.24 13:56 */ public class Builder { public static void main(String[] args) { Villa villa = new Villa(); HouseDirector houseDirector = new HouseDirector(villa); House house = houseDirector.constructHouse(); Bungalow bungalow =new Bungalow(); houseDirector.setHouseBuilder(bungalow); House house1 =houseDirector.constructHouse(); } }
When we add buildings, we only need to add TallBuilding
package com.wangscaler.builder; /** * @author wangscaler * @date 2021.06.24 13:56 */ public class TallBuilding extends HouseBuilder { @Override public void layingFoundation() { System.out.println("Building foundation"); } @Override public void buildWall() { System.out.println("Building wall"); } @Override public void sealRoof() { System.out.println("Building capping"); } }
Builder mode in source code
StringBuilder in JDK
We open the source code
package java.lang; public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence { /** use serialVersionUID for interoperability */ static final long serialVersionUID = 4383685877147921099L; /** * Constructs a string builder with no characters in it and an * initial capacity of 16 characters. */ public StringBuilder() { super(16); } /** * Constructs a string builder with no characters in it and an * initial capacity specified by the {@code capacity} argument. * * @param capacity the initial capacity. * @throws NegativeArraySizeException if the {@code capacity} * argument is less than {@code 0}. */ public StringBuilder(int capacity) { super(capacity); } /** * Constructs a string builder initialized to the contents of the * specified string. The initial capacity of the string builder is * {@code 16} plus the length of the string argument. * * @param str the initial contents of the buffer. */ public StringBuilder(String str) { super(str.length() + 16); append(str); } /** * Constructs a string builder that contains the same characters * as the specified {@code CharSequence}. The initial capacity of * the string builder is {@code 16} plus the length of the * {@code CharSequence} argument. * * @param seq the sequence to copy. */ public StringBuilder(CharSequence seq) { this(seq.length() + 16); append(seq); } @Override public StringBuilder append(Object obj) { return append(String.valueOf(obj)); } @Override public StringBuilder append(String str) { super.append(str); return this; } public StringBuilder append(StringBuffer sb) { super.append(sb); return this; } @Override public StringBuilder append(CharSequence s) { super.append(s); return this; } /** * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder append(CharSequence s, int start, int end) { super.append(s, start, end); return this; } @Override public StringBuilder append(char[] str) { super.append(str); return this; } /** * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder append(char[] str, int offset, int len) { super.append(str, offset, len); return this; } @Override public StringBuilder append(boolean b) { super.append(b); return this; } @Override public StringBuilder append(char c) { super.append(c); return this; } @Override public StringBuilder append(int i) { super.append(i); return this; } @Override public StringBuilder append(long lng) { super.append(lng); return this; } @Override public StringBuilder append(float f) { super.append(f); return this; } @Override public StringBuilder append(double d) { super.append(d); return this; } /** * @since 1.5 */ @Override public StringBuilder appendCodePoint(int codePoint) { super.appendCodePoint(codePoint); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder delete(int start, int end) { super.delete(start, end); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder deleteCharAt(int index) { super.deleteCharAt(index); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder replace(int start, int end, String str) { super.replace(start, end, str); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int index, char[] str, int offset, int len) { super.insert(index, str, offset, len); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int offset, Object obj) { super.insert(offset, obj); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int offset, String str) { super.insert(offset, str); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int offset, char[] str) { super.insert(offset, str); return this; } /** * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int dstOffset, CharSequence s) { super.insert(dstOffset, s); return this; } /** * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int dstOffset, CharSequence s, int start, int end) { super.insert(dstOffset, s, start, end); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int offset, boolean b) { super.insert(offset, b); return this; } /** * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int offset, char c) { super.insert(offset, c); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int offset, int i) { super.insert(offset, i); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int offset, long l) { super.insert(offset, l); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int offset, float f) { super.insert(offset, f); return this; } /** * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder insert(int offset, double d) { super.insert(offset, d); return this; } @Override public int indexOf(String str) { return super.indexOf(str); } @Override public int indexOf(String str, int fromIndex) { return super.indexOf(str, fromIndex); } @Override public int lastIndexOf(String str) { return super.lastIndexOf(str); } @Override public int lastIndexOf(String str, int fromIndex) { return super.lastIndexOf(str, fromIndex); } @Override public StringBuilder reverse() { super.reverse(); return this; } @Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); } private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); s.writeInt(count); s.writeObject(value); } /** * readObject is called to restore the state of the StringBuffer from * a stream. */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); count = s.readInt(); value = (char[]) s.readObject(); } }
Found that he inherited AbstractStringBuilder
abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value; /** * The count is the number of characters used. */ int count; /** * This no-arg constructor is necessary for serialization of subclasses. */ AbstractStringBuilder() { } /** * Creates an AbstractStringBuilder of the specified capacity. */ AbstractStringBuilder(int capacity) { value = new char[capacity]; } public AbstractStringBuilder append(Object obj) { return append(String.valueOf(obj)); } public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } // Documentation in subclasses because of synchro difference public AbstractStringBuilder append(StringBuffer sb) { if (sb == null) return appendNull(); int len = sb.length(); ensureCapacityInternal(count + len); sb.getChars(0, len, value, count); count += len; return this; } /** * @since 1.8 */ AbstractStringBuilder append(AbstractStringBuilder asb) { if (asb == null) return appendNull(); int len = asb.length(); ensureCapacityInternal(count + len); asb.getChars(0, len, value, count); count += len; return this; } // Documentation in subclasses because of synchro difference @Override public AbstractStringBuilder append(CharSequence s) { if (s == null) return appendNull(); if (s instanceof String) return this.append((String)s); if (s instanceof AbstractStringBuilder) return this.append((AbstractStringBuilder)s); return this.append(s, 0, s.length()); } private AbstractStringBuilder appendNull() { int c = count; ensureCapacityInternal(c + 4); final char[] value = this.value; value[c++] = 'n'; value[c++] = 'u'; value[c++] = 'l'; value[c++] = 'l'; count = c; return this; } @Override public AbstractStringBuilder append(CharSequence s, int start, int end) { if (s == null) s = "null"; if ((start < 0) || (start > end) || (end > s.length())) throw new IndexOutOfBoundsException( "start " + start + ", end " + end + ", s.length() " + s.length()); int len = end - start; ensureCapacityInternal(count + len); for (int i = start, j = count; i < end; i++, j++) value[j] = s.charAt(i); count += len; return this; } public AbstractStringBuilder append(char[] str) { int len = str.length; ensureCapacityInternal(count + len); System.arraycopy(str, 0, value, count, len); count += len; return this; } public AbstractStringBuilder append(char str[], int offset, int len) { if (len > 0) // let arraycopy report AIOOBE for len < 0 ensureCapacityInternal(count + len); System.arraycopy(str, offset, value, count, len); count += len; return this; } public AbstractStringBuilder append(boolean b) { if (b) { ensureCapacityInternal(count + 4); value[count++] = 't'; value[count++] = 'r'; value[count++] = 'u'; value[count++] = 'e'; } else { ensureCapacityInternal(count + 5); value[count++] = 'f'; value[count++] = 'a'; value[count++] = 'l'; value[count++] = 's'; value[count++] = 'e'; } return this; } @Override public AbstractStringBuilder append(char c) { ensureCapacityInternal(count + 1); value[count++] = c; return this; } public AbstractStringBuilder append(int i) { if (i == Integer.MIN_VALUE) { append("-2147483648"); return this; } int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1 : Integer.stringSize(i); int spaceNeeded = count + appendedLength; ensureCapacityInternal(spaceNeeded); Integer.getChars(i, spaceNeeded, value); count = spaceNeeded; return this; } public AbstractStringBuilder append(long l) { if (l == Long.MIN_VALUE) { append("-9223372036854775808"); return this; } int appendedLength = (l < 0) ? Long.stringSize(-l) + 1 : Long.stringSize(l); int spaceNeeded = count + appendedLength; ensureCapacityInternal(spaceNeeded); Long.getChars(l, spaceNeeded, value); count = spaceNeeded; return this; } public AbstractStringBuilder append(float f) { FloatingDecimal.appendTo(f,this); return this; } public AbstractStringBuilder append(double d) { FloatingDecimal.appendTo(d,this); return this; } }
Taking the append method as an example, we find that it implements the abstract class Appendable
/* * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * */ package java.lang; import java.io.IOException; public interface Appendable { Appendable append(CharSequence csq) throws IOException; Appendable append(CharSequence csq, int start, int end) throws IOException; Appendable append(char c) throws IOException; }
This Appendable can be said to be the abstract builder of our builder pattern. If we create abstract methods here, our AbstractStringBuilder is the concrete builder who implements these abstract methods, and StringBuilder acts as the commander
Override public StringBuilder append(Object obj) { return append(String.valueOf(obj)); }
And acted as a concrete builder
@Override public StringBuilder append(String str) { super.append(str); return this; }
summary
Four roles of builder
- Product role: a specific object (house) containing multiple components (foundation, wall and roof)
- Abstract Builder: it includes the interface of abstract methods for creating various parts of the product (foundation, wall and roof), and generally includes the method (buildHouse) for returning the final product.
- Specific Builder: realize the interface and realize the method of building and assembling various components (foundation building, wall building and capping)
- Commander: call the method of building parts to complete the creation of objects (assemble the house).
Usage scenario:
1. The products created by the builders generally have more in common (the building is built with foundation, wall and roof). If the difference is large, it is not suitable for use.
2. Factory mode is generally used to create simple objects, and builder mode is generally used to create complex objects (more than 5 components)
3. The same method and different execution order produce different results.
4. Multiple assemblies or parts can be assembled into one object, but the results are different.
5. The product class is very complex, or different call sequences in the product class have different effects.
6. Initializing an object is particularly complex, with many parameters, and many parameters have default values.
reference material
Design mode – adapter mode
Adapter mode adapter mode is to convert an interface of a class into another interface expected by the client. For example, our headphones are round, and the mobile phone has only a square charging port, so we can use the adapter to convert. The adapter is the adapter.
Adapter mode
The adapter mode is to convert the interface of a class into another interface expected by the client. For example, our headphones are round, while the mobile phone has only a square charging port. How do we use headphones? At this time, the adapter is the adapter, which converts the square port interface into the round port interface.
Class Adapter
For example, the earphone port of our mobile phone is square
Phone
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 11: 06 */ public class Phone { public String rectanglePort() { String src = "performance strict to the script"; System.out.println(src + "Headphone port"); return src; } }
Our headphones need a round mouth. We have a round interface
RoundPort
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 11: 06 */ public interface RoundPort { public String roundPort(); }
So our adapter
PhoneAdapter
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 11: 06 */ public class PhoneAdapter extends Phone implements RoundPort { @Override public String roundPort() { String src = rectanglePort(); if (src.equals("performance strict to the script")) { //After a complex conversion process src = "Round mouth"; } return src; } }
Our earphones have a function of listening to songs. At this time, if we want to listen to songs, we must rely on the round mouth
HeadPhone
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 11: 06 */ public class HeadPhone { public void listen(RoundPort roundPort) { if (roundPort.roundPort().equals("Round mouth")) { System.out.println("Listening to a song"); } else { System.out.println("Unable to connect"); } } }
main
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 11: 06 */ public class Adapter { public static void main(String[] args) { HeadPhone headPhone = new HeadPhone(); headPhone.listen(new PhoneAdapter()); } }
At this time, we must inherit the Phone to use the rectanglePort method to implement the Adapter. At this time, all methods of the Phone will be exposed to the Adapter, which increases the use cost. Because Java is single inheritance, the RoundPort must be an interface.
object adapter
According to the "composite Reuse Principle", the inheritance relationship is transformed into association relationship. The object adapter is also the most commonly used adapter pattern.
Therefore, you only need to modify the PhoneAdapter to
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 11: 46 */ public class PhoneAdapter1 implements RoundPort { private Phone phone; public PhoneAdapter1(Phone phone) { this.phone = phone; } @Override public String roundPort() { if (phone != null) { String src = phone.rectanglePort(); if (src.equals("performance strict to the script")) { src = "Round mouth"; } return src; } return null; } }
main
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 11: 06 */ public class Adapter { public static void main(String[] args) { HeadPhone headPhone = new HeadPhone(); headPhone.listen(new PhoneAdapter1(new Phone())); } }
This approach is not only more flexible, but also cost-effective.
Interface adapter
Also known as the default adapter mode, this mode can be used when it is not necessary to implement all the methods provided by the interface. Unnecessary methods only need to implement empty methods.
Provide a universal adapter.
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.21 13:23 */ public interface UniversalAdapter { String roundPort(); String rectanglePort(); String squarePort(); }
Let our adapter implement the empty method by default and rewrite it when it is used
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 11: 46 */ public class PhoneAdapter2 implements UniversalAdapter { private Phone phone; public PhoneAdapter2(Phone phone) { this.phone = phone; } @Override public String roundPort() { return phone.rectanglePort(); } @Override public String rectanglePort() { return phone.rectanglePort(); } @Override public String squarePort() { return phone.rectanglePort(); } }
Override on use
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 13: 44 */ public class HeadPhone1 { public void listen() { PhoneAdapter2 phoneAdapter2 = new PhoneAdapter2(new Phone()) { @Override public String roundPort() { if (super.roundPort().equals("performance strict to the script")) { String src = "Round mouth"; return src; } return null; } }; if (phoneAdapter2.roundPort().equals("Round mouth")) { System.out.println("Listen to the music"); } else { System.out.println("Connectivity failure"); } } }
main
package com.wangscaler.adapter; /** * @author wangscaler * @date 2021.06.25 11: 06 */ public class Adapter { public static void main(String[] args) { HeadPhone1 headPhone = new HeadPhone1(); headPhone.listen(); } }
This method is easy to expand. For example, we have square port headphones, which can also be directly implemented by rewriting squarePort. This approach is common in Android development.
Builder mode in source code
HandlerAdapter in spring MVC
public class DispatcherServlet extends FrameworkServlet { java@Nullable private List<HandlerMapping> handlerMappings; @Nullable private List<HandlerAdapter> handlerAdapters; public DispatcherServlet() { this.setDispatchOptionsRequest(true); } public DispatcherServlet(WebApplicationContext webApplicationContext) { super(webApplicationContext); this.setDispatchOptionsRequest(true); } protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { Iterator var2 = this.handlerAdapters.iterator(); while(var2.hasNext()) { HandlerAdapter adapter = (HandlerAdapter)var2.next(); if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }
When we initiate a request, such as a login request, it will pass through the dispatcher servlet. In doDispatch, according to mappedhandler = this getHandler(processedRequest);, Get the login handler from the request. Then according to handleradapter ha = this getHandlerAdapter(mappedHandler.getHandler()); He will iterate through gethandleradapter to find the appropriate adapter.
Adapter HandlerAdapter source code
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.web.servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.lang.Nullable; public interface HandlerAdapter { boolean supports(Object var1); @Nullable ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception; long getLastModified(HttpServletRequest var1, Object var2); }
As can be seen from the figure below, six classes implement our interface
Then by executing MV = ha handle(processedRequest, response, mappedHandler.getHandler()); To execute our Handler. After executing our business interface, we will get the ModelAndView we returned, and then find the corresponding view resource to return.
Here, DispatcherServlet is the user, and HandlerAdapter is the expected interface
summary
The adapter is not added in the detailed design, but to solve the problem of the project in service. The current interface is not suitable for use. We can use the adapter mode to modify the interface programming of a normal running system with motivation.
Using adapters will increase the complexity of the system and make it more difficult to read the code. Excessive use of adapters will make the system code messy, so try to avoid using adapters.
The core is to "transform the interface of a class into another interface expected by the client"