Design pattern elaboration
1, Course guidance and introduction
1.1 navigation in this chapter
- This chapter will explain UML through the following six aspects: definition, characteristics, classification, class diagram, sequence diagram and memory skills
- URL definition:
- Unified Modeling Language (UML)
- Non patent third generation modeling and specification language
- URL features:
- UML is an open method
- An open method for describing, visualizing, building and writing the artifacts of an object-oriented, software intensive system under development.
- UML presents a series of best engineering practices, which have been verified to be effective in modeling large-scale and complex systems, especially at the software architecture level.
- UML 2.2 classification: (14 graphics are defined in UML 2.2, and the classification is as follows):
- Structural graphics: it emphasizes systematic modeling:
- Static diagram (class diagram, object diagram, package diagram)
- Implementation diagram (component diagram, deployment diagram)
- profile
- Composite structure diagram
- Behavioral graphics: emphasize the events triggered in system modeling:
- Activity diagram
- state diagram
- Use case diagram
- Interactive graphics: it is a subset of behavioral graphics, emphasizing the data flow in the system model:
- Communication diagram
- Interaction overview diagram (UML 2.0)
- Sequence diagram (UML 2.0)
- Time chart (UML 2.0)
- Structural graphics: it emphasizes systematic modeling:
- UML class diagram:
- Class Diagram: used to represent the static relationship among classes, interfaces, instances, etc
- Although the name is class diagram, there are not only classes in class diagram
- Memory skills:
- UML arrow direction: from subclass to parent
- When defining a subclass, you need to specify the parent class through the extends keyword
- The subclass must know the definition of the parent class, but the parent class does not know the definition of the subclass
- Only when you know each other's information can you point to each other
- So the arrow direction is from the child class to the parent class
- Memory skills - Implementation - inheritance | dotted line - Implementation
- White triangle arrows: inheritance or implementation
- UML arrow direction: from subclass to parent
2, Seven principles of software design
2.1 navigation in this chapter
- This chapter will explain opening and closing, dependency inversion, single responsibility, interface isolation principle and Dimitri's law
- The design principle is not mandatory compliance, but trade-offs according to the actual business scenarios to achieve the most appropriate degree.
2.2 opening and closing principle
- Opening and closing principle theory:
- Principle of opening and closing: a software entity, such as classes, modules and functions, should be open to extensions and closed to modifications. (like elastic clock in, it works eight hours a day, which is immutable, but it comes early and leaves early, comes late and leaves late, and is open to extensions.)
- Build the framework with abstraction and extend the details with implementation (use fixed abstraction to define functions, and then use implementation classes to implement specific logic.)
- Advantages: improve the reusability and maintainability of the software system
The opening and closing principle is oriented to abstract programming, because abstraction is stable.
- Opening and closing principle coding: we want to implement a function to obtain the id, name and price of a course, so the code we define is as follows
- Create an interface:
public interface ICourse{ Integer getId(); String getName(); Double getPrice(); }
- Create an implementation class:
public class JavaCourse implements ICourse{ private Integer Id; private String name; private Double price; public JavaCourse(Integer id, String name, Double price){ this.Id = id; this.name = name; this.price = price; } public Integer getId(){ return this.Id; } public String getName(){ return this.name; } public Double getPrice(){ return this.price; } }
- In subsequent iterations, we need to give a discount of 20% for a specified course. So how can we improve our code? Change the price acquisition interface to a discount interface? It does not comply with the opening and closing principle and meets this function, but does not meet other functions. Therefore, the solution is: we discount a java course, so we can use Java The course class (JavaCourse) inherits and implements a Java discount course class, which not only ensures the stability of the interface, but also realizes the functions, and is not coupled. This is the * * opening and closing principle of opening and closing to extensions and closing to modifications! * * the implementation class is created as follows:
public class JavaDiscountCourse extends JavaCourse{ public JavaDiscountCourse(Integer id, String name, Double price){ super(id,name,price); } public Double getOriginPrice(){ return super.getPrice(); } @Override public Double getPrice(){ return super.getPrice()*0.8; } }
- Test class
public class Test{ public static void main(String[] args){ ICourse iCourse = new JavaDiscountCourse(11,"Chinese Course",300d); JavaDiscountCourse javaCourse = (JavaDiscountCourse)iCourse; System.out.println("curriculum ID"+javaCourse.getId() + "Course name:"+ javaCourse.getName()+"Course original price:"+javaCourse.getPrice()); } }
- The hierarchical structure is shown in the figure:
- Create an interface:
2.3 Dependency Inversion Principle
- Theory:
- High level modules should not rely on low-level modules, and both should rely on their abstraction
- Abstractions should not rely on details; details should rely on abstractions
- Programming for interfaces, not for implementations
- Using the dependency inversion principle can reduce the coupling lines between classes, improve system stability, improve code readability and maintainability, and reduce the risk caused by program modification.
- coding: (the architecture based on abstraction is more stable than the architecture based on detail, and the interface can formulate specifications and contracts)
- v1 version: implementation oriented programming: the requirements are as follows: Geely needs to learn Java and front-end courses:
- Create Geely class:
public class Geely{ public void studyJavaCourse(){ System.out.ptintln("Geely I'm learning Java curriculum"); } public void studyFECourse(){ System.out.println("Geely Learning front-end courses"); } }
- To create a test class:
public class Test{ Geely geely = new Geely(); geely.studyJavaCourse(); geely.studyFECourse(); }
- Now, what if Geely wants to learn Python? Add Python methods to Geely class as follows:
public class Geely{ public void studyJavaCourse(){ System.out.ptintln("Geely I'm learning Java curriculum"); } public void studyFECourse(){ System.out.println("Geely Learning front-end courses"); } public void studyPythonCourse(){ System.out.println("Geely I'm learning Python curriculum"); } }
We found that whenever our superiors want to need a function, they need a low level to implement it, and its stability is very poor. According to the dependency lead principle, high-level modules should not rely on low-level modules, which does not comply with the dependency inversion principle. How to solve it? Use abstraction to improve the code.
- Create Geely class:
- v2 Version (method abstraction) uses abstraction to achieve the principle of dependency leading;
- Define abstract interfaces:
public interface ICourse{ void studyCourse(); }
- Learning java course implementation class:
public class JavaCourse implements ICourse{ @Override public void studyCourse(){ System.out.println("Geely I'm learning Java curriculum"); } }
- Learning front-end course implementation class:
public class FECourse implements Icourse{ @Override public void studyCourse(){ System.out.println("Geely I'm learning FE curriculum"); } }
- Geely class:
public class Geely{ public void studyCourse(Icourse icourse){ iCourse.studyCourse(); } }
- Test class:
public class Test{ public static void main(String[] args){ Geely geely = new Geely(); geely.studyCourse(new JavaCourse()); geely.studyCourse(new FECourse()); } }
Using abstraction, we can create different implementation classes as needed, which not only achieves the interface and maintains the stability of interface functions, but also complies with the dependency inversion principle. The upper application no longer depends on Geely, but implements the corresponding Course class as needed;
- Define abstract interfaces:
- v3 version: injected through constructor:
- The interface and interface implementation class are consistent with v2 version.
- Geely class:
public class Geely{ private ICourse iCourse; public Geely(ICourse iCourse){ this.iCourse = iCourse; } public void studyCourse(ICourse iCourse){ iCourse.studyCourse(); } }
- Test class:
public class Test{ public static void main(String[] args){ Geely geely = new Geely(new JavaCourse()); geely.studyImooCourse(); } }
Although the use of constructor injection avoids the need for separate interfaces for the methods in it, construction injection will lead to the use of only one course. If Geely wants to learn Java and front-end courses at the same time, it needs to create two classes. Obviously, this is not what we want. set injection can solve this problem.
- v4 version, using set injection:
- Interface and interface implementation class are consistent with v2 version
- Geely:
public class Geely{ public void setCourse(ICourse iCourse){ this.iCourse = iCourse; } private ICourse iCourse; public void studyCourse(){ iCourse.studyCourse(); } }
- v1 version: implementation oriented programming: the requirements are as follows: Geely needs to learn Java and front-end courses:
In the low-level module, we can extend it, and in the upper module, we meet the dependency lead principle.
2.4 single responsibility
- Theory:
- Definition: do not have more than one reason for class change;
- A class / interface / method is responsible for only one responsibility
- Advantages: reduce class complexity, improve class readability, improve system maintainability and reduce risks caused by changes.
- coding:
- The requirements are as follows: implement a class where ostriches walk with their feet and other birds fly with their wings:
- Counterexample, directly implement the judgment in the class:
public class Bird{ public mainMoveMode(String birdName){ if("ostrich".equals(birdName)){ System.out.println(birdName+"Walk with your feet"); }else{ System.out.println(birdName+"Fly with wings"); } } }
The example does not use a single responsibility. If there are other situations, such as so and so birds crawling and so and so birds jumping, will it be necessary to add a lot of if else judgment? We can solve this problem with a single responsibility. For example, we can create a flying bird class and a foot walking bird class, so that the corresponding birds can use the corresponding action class to complete the function without causing too high coupling, and meet the single responsibility;
- Counterexample, directly implement the judgment in the class:
- The above requirements illustrate the single responsibility of the implementation class. We should also meet the single principle in the definition of the interface. As follows:
- The old interfaces are as follows:
public interface ICourse{ String getCourseName(); byte[] getCourseVideo(); void studyCourse(); void refundCourse(); }
The problem with this interface is that the above two methods are to obtain courses, while the following are to learn courses and refund courses. Methods with different responsibilities defined in an interface can lead to confusion. For example, for a learning class, I only want to implement getCourseName() method and getCourseVideo() method, but all of them must be implemented after inheriting this interface, resulting in different responsibilities.
- The transformation is as follows:
- We can classify obtaining course names and course videos into one interface, learning courses and refunding courses into one interface. When we need to implement the interfaces on both sides, we can implement both interfaces in the implementation class at the same time, or we can implement only what we need alone, which is more flexible.
- The class structure is as follows:
- The old interfaces are as follows:
- The requirements are as follows: implement a class where ostriches walk with their feet and other birds fly with their wings:
2.5 interface isolation principle
- Theory:
- Definition: use multiple special interfaces instead of a single general interface. The client should not rely on the interfaces it does not need
- The dependence of a class on a class should be based on the smallest interface
- Establish a single interface, not a huge and bloated interface
- Try to refine the interface and minimize the methods in the interface
- Pay attention to the principle of moderation and be moderate;
- The use of interface isolation principle is in line with the design idea of high cohesion and low coupling, so that the class has good readability, scalability and maintainability.
In the process of designing classes, we should also consider possible changes in the future and make some predictions.
- coding:
- Counterexample:
- Define interface:
- dog implementation class:
- bird implementation class:
We found a problem that the dog implements the animal like interface, but it can't implement the fly method because the dog can't fly. At the same time, birds implement the swim method, but birds can't swim. This shows that the interface isolation principle is not implemented, resulting in some methods that are not applicable to the implementation class.
- Define interface:
- By refining the interface, eat, fly and swim are divided into three interfaces to realize interface isolation:
- As can be seen from the figure, we have implemented three actions, defining eat(), swim(), and fly() methods respectively. When we need to implement dog, we only need to inherit the IEatAnimalAction interface and iswimmanimalaction interface.
- Counterexample:
Refine the interface appropriately to avoid class expansion;
2.6 explanation of Demeter's law
-
Theory:
- Definition: an object should have the least understanding of other objects. Also known as the least known principle
- Minimize coupling between classes
- Emphasize only communicating with friends and not talking to strangers;
- Friend: the classes that appear in the input and output parameters of member variables and methods are called member friend classes, while the classes that appear in the method body do not belong to friend classes;
-
coding:
- Before disobeying Demeter's Law:
- Boss class:
public class Boss{ public void commandCheckNumber(TeamLeader temLeader){ List<Course> courseList= new ArrayList<Course>(); for(int i = 0; i<20 ; i++){ courseList.add(new Course()); } teamLeader.checkNumberOfCourses(courseList); } }
- TeamLeader class
public class TeamLeader{ public void checkNumberOfCourses(List<Course> courseList){ System.out.println("The number of online courses is CSDN Dark surplus:"+courseList.size()); } }
- Boss class:
- After following Dimitri's Law:
- Boss class:
public class Boss{ public void commandCheckNumber(TeamLeader teamLeader){ teamLeader.checkNumberOfCourse(); } }
- TeamLeader class:
public class TeamLeader{ public void checkNumberOfCourses(){ List<Course> courseList= new ArrayList<Course>(); for(int i = 0; i< 20; i++){ courseList.add(new Course()); } System.out.println("The number of online courses is:"+courseList.size()); } }
- Boss class:
- Before disobeying Demeter's Law:
Boss wants to know the total number of existing courses from TeamLeader. The calling relationship between them should be boss - > TeamLeader - > Course. Boss is not directly related to Course, so Course class should not appear in the methods of boss class. This is Dimitri's law;