1. Single responsibility principle
Basic introduction
For classes,A class should be responsible for only one responsibility. For example, class A should be responsible for two different responsibilities, responsibility 1 and responsibility 2. When the requirement of responsibility 1 changes and A changes, it may cause an error in the execution of Responsibility 2, so the granularity of class A should be divided into A1 and A2.
Single responsibility notes and details:
- Reduce the complexity of classes so that each class is responsible for only one responsibility
- Improve class readability and maintainability
- Reduce the risk of change
- In general, we should adhere to the single responsibility principle, which can only be violated at the code level if the logic is simple enough: the single responsibility principle can only be maintained at the method level if the number of methods in the class is small enough.
Application, for example,
An example of a vehicle
- Plan 1
public class SingleResponsibility1 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); Vehicle. Run (" car "); Vehicle. The run (" aircraft "); Vehicle. The run (" ship "); } // Run (); // Run (); // Run (); Class Vehicle {public void run(String Vehicle) {system.out.println (Vehicle + "walking on the highway "); }}Copy the code
- Scheme 2
public class SigleResponsibility2 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); Vehicle. Run (" car "); AirVehicle airVehicle = new AirVehicle(); AirVehicle. Run (" aircraft "); WaterVehicle waterVehicle = new WaterVehicle(); WaterVehicle. Run (" ship "); }} / / / / 1 second scheme analysis, follow the single responsibility / / 2, relative to the plan 1, change is very big, the kind of decomposition, and modify client / / 3, the improvement: Class RoadVehicle {public void run(String Vehicle) {system.out.println (Vehicle + "run on the road "); }} class AirVehicle {public void run(String vehicle) {system.out.println (vehicle + "flying "); }} class WaterVehicle {###### public void run(String vehicle) {system.out.println (vehicle + "run in water "); }}Copy the code
- Solution 3:
public class SingleResponsibility3 { public static void main(String[] args) { Vehicle3 vehicle = new Vehicle3(); Vehicle. Run (" car "); Vehicle. RunAir (" aircraft "); Vehicle. RunWater (" ship "); } //1. This method does not change the original class too much, but only adds methods. //2. Not strictly following a single responsibility, Class Vehicle3 {public void run(String vehicle) {system.out.println (vehicle + "Walk on the road "); } public void runAir(String vehicle) {system.out.println (vehicle + "flying in the sky "); } public void runWater(String vehicle) {system.out.println (vehicle + "run in water "); }}Copy the code
2. Interface isolation principle
Basic introduction
A client should not rely on interfaces it does not need, i.e. a class’s dependency on another class should be based on the smallest interface.
Application, for example,
- As shown in figure:
- A depends on B through Interface1, but A only uses the 1,2, and 3 methods of the interface
- C relies on D through Interface1, but A only uses the 1,4, and 5 methods of the interface
According to the principle of interface isolation: Interface1 should be split into several independent interfaces. Class A and Class C should establish dependencies on the interfaces they need respectively. In other words, the principle of interface isolation should be adopted.
3. Rely on the reverse principle
Basic introduction
- High-level modules should not depend on low-level modules; both should depend on their abstractions.
- Abstractions should not depend on details; details should depend on abstractions
- The central idea behind dependency inversion is programming to interfaces
- The dependency inversion principle is based on the design idea that abstractions are more stable than detailed polygons, and that abstractions are more stable than detail-based architectures. In Java, abstraction refers to the interface or abstract class, and the detail is the concrete implementation class.
- The purpose of using interfaces or abstractions is to define specifications that do not involve any specific operations, leaving the task of presenting the details to their implementation classes.
Application, for example,
Programming to complete the function of Person sending messages scheme 1
public class DependencyInversion { public static void main(String[] args) { new Person().receive(new Email()); }} class Email {public String getInfo() {return "Hello World!" ; //2. If you want to get wechat, SMS and other information, you need to add a new class, and at the same time, you need to add a corresponding receiving method. //3. Solution: Introduce an abstract interface IReceiver, which represents the receiver, so that the Person class depends on the interface IReceive. Class Person {public void receive(email email) {system.out.println (email.getInfo()); }}Copy the code
Plan 2, improve plan 1
public class DependencyInversionImprove { public static void main(String[] args) { new Person2().receive(new Email2()); new Person2().receive(new Wechat()); } } interface IReceiver { String getInfo(); } class Email2 implements IReceiver {public String implements getInfo() {return "String implements IReceiver"; ; }} class Wechat implements IReceiver {public String implements IReceiver () {return "Wechat implements: Hello World!" ; } class Person2 {public void receive(IReceiver Receive) {system.out.println (receive.getinfo ()); }}Copy the code
Three ways in which dependencies are passed
- As interface
public class DependencyTransmit { public static void main(String[] args) { ITV tv = new XiaoMiTV(); IOpenAndClose openAndClose = new OpenAndClose(); openAndClose.open(tv); } } interface IOpenAndClose { void open(ITV tv); } interface ITV { void play(); } class OpenAndClose implements IOpenAndClose { @Override public void open(ITV tv) { tv.play(); }} class XiaoMiTV implements ITV {@override public void play() {system.out.println (" implements ITV "); }}Copy the code
- Constructor transfer
public class DependencyTransmit2 { public static void main(String[] args) { ITV2 tv = new XiaoMiTV2(); OpenAndClose2 openAndClose = new OpenAndClose2(tv); openAndClose.open(); } } interface IOpenAndClose2 { void open(); } interface ITV2 { void play(); } class OpenAndClose2 implements IOpenAndClose2 { private ITV2 tv; public OpenAndClose2(ITV2 tv) { this.tv = tv; } @Override public void open() { tv.play(); }} class XiaoMiTV2 implements ITV2 {@override public void play() {system.out.println (" implements ITV2 "); }}Copy the code
- Setter method passing
public class DependencyTransmit { public static void main(String[] args) { ITV tv = new XiaoMiTV(); IOpenAndClose openAndClose = new OpenAndClose(); openAndClose.open(tv); } } interface IOpenAndClose { void open(ITV tv); } interface ITV { void play(); } class OpenAndClose implements IOpenAndClose { @Override public void open(ITV tv) { tv.play(); }} class XiaoMiTV implements ITV {@override public void play() {system.out.println (" implements ITV "); }}Copy the code
4. The Richter substitution principle
Basic introduction
- If there is an object T2 of type T2 for each object T1 of type T1, such that the behavior of the program P defined in T1 does not change when all objects T1 are replaced by T2, then type T2 is a subtype of T1. In other words, all references to the base class must transparently use its subclass objects.
- When using inheritance, follow the Richter substitution principle, try not to copy the methods of the parent class in the subclass.
- The Richter substitution principle tells us that inheritance actually makes two classes more coupled, and where appropriate, aggregation, composition, and dependency can be used to solve the problem.
Application, for example,
Some problems when applying inheritance
public class Liskov { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.fun1(11, 3)); System.out.println("1-8=" + a.fun1(1, 8)); B b = new B(); System.out.println("11-3=" + b.fun1(11, 3)); System.out.println("1-8=" + b.fun1(1, 8)); System.out.println("1-8+9=" + b.fun2(1, 8)); } } class A { public int fun1(int a, int b) { return a - b; } } class B extends A { @Override public int fun1(int a, int b) { return a + b; } public int fun2(int a, int b) { return fun1(a, b) + 9; }}Copy the code
Class B duplicates the methods of the parent class, causing errors in the original function. In practice, we often override the methods of the parent class to complete new functions, which looks simple, but the reusability of the whole integration system is poor, especially when the polymorphism is running frequently. The general approach is: the original parent class and child class inherit a more popular base class, the original inheritance relationship is removed, using dependency, aggregation, combination and other relationships to replace.
Code implementation
public class LiskovImprove { public static void main(String[] args) { A2 a = new A2(); System.out.println("11-3=" + a.fun1(11, 3)); System.out.println("1-8=" + a.fun1(1, 8)); B2 b = new B2(); Println ("11+3=" + b.fun1(11, 3)); println("11+3=" + b.fun1(11, 3)); System.out.println("1+8=" + b.fun1(1, 8)); System.out.println("1-8+9=" + b.fun2(1, 8)); } } interface Base { int fun1(int a, int b); } class A2 implements Base { public int fun1(int a, int b) { return a - b; } } class B2 implements Base { A2 a = new A2(); @Override public int fun1(int a, int b) { return a + b; } public int fun2(int a, int b) { return this.a.fun1(a, b) + 9; }}Copy the code
5. Open Close Principle
Basic introduction
- Software design should be open for extension (the server) and closed for modification (the caller); Build the framework with abstractions and extend the details with implementations.
- When software requirements change, try to achieve the change by extending the behavior of software entities rather than by modifying existing code.
- To follow other principles in programming and to use design patterns is to follow the open closed principle
Application, for example,
Complete a traditional class diagram for drawing graphics
public class GraphicEditor { public static void main(String[] args) { GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawCircle(new Circle()); } public void drawShape(Shape shape) { if (shape.getType() == 1) { drawRectangle(shape); } else if (shape.getType() == 2) { drawCircle(shape); }} private void drawRectangle(Shape Shape) {system.out.println (" drawRectangle "); } private void drawCircle(Shape) {system.out.println (" drawCircle "); } } @Data class Shape { int type; } class Rectangle extends Shape { public Rectangle() { super.type = 1; } } class Circle extends Shape { public Circle() { super.type = 2; }}Copy the code
The code analysis
- The advantage is that it is easy to understand, simple and easy to operate
- Determine if it violates the design pattern open closed principle, which is open for extension and closed for modification.
- If we want to add a type of graphics, such as: triangle, the modification of more places, even will affect the existing graphics.
Traditional scheme improvement ideas: the creation of Shape class made abstract class, and through an abstract draw method, let the subclass to achieve, the use of the code does not need to modify, to meet the open closed principle
public class GraphicEditorImprove { public static void main(String[] args) { GraphicEditorImprove graphicEditorImprove = new GraphicEditorImprove(); graphicEditorImprove.drawShape(new Rectangle2()); graphicEditorImprove.drawShape(new Circle2()); graphicEditorImprove.drawShape(new Triangle()); } public void drawShape(Shape2 shape) { shape.draw(); } } @Data abstract class Shape2 { abstract void draw(); } class Rectangle2 extends Shape2 {@override void draw() {system.out.println (" draw rectangle "); }} class Circle2 extends Shape2 {@override void draw() {system.out.println (); }} class Triangle extends Shape2 {@override void draw() {system.out.println (); }}Copy the code
6. The Demeter rule
Basic introduction
- An object should have minimal knowledge of other objects
- The closer the relationship between classes, the greater the coupling degree
- Demitt’s law, also known as the principle of least knowledge, states that a class should know as little as possible about the classes it depends on. In other words, it should try to encapsulate the logic of the dependent classes, no matter how complex, and not disclose any information except providing public methods
- There is a simpler definition of Demeter’s method: to write only to direct friends.
- Direct friend: Every object is coupled to other objects. As long as two objects are coupled to each other, they are said to be friends. There are many ways of coupling, such as dependency, inheritance, aggregation, composition, etc. Among them, we call the classes that appear in member variables, method parameters, and method return values as direct friends, and the classes that appear in local variables are not direct friends. That is to say, unfamiliar classes should not appear in the class in the form of local variables
Application, for example,
There is a school with various colleges and headquarters under it. Now it is required to print out the id of the school headquarters staff and the id of the college staff. Traditional code:
public class Demeter { public static void main(String[] args) { SchoolManager schoolManager = new SchoolManager(); schoolManager.printAllEmployee(new CollegeManager()); } } @Data @AllArgsConstructor class CollegeEmployee { private Long id; } @Data @AllArgsConstructor class Employee { private Long id; } class SchoolManager { public List<Employee> getAllEmployee() { return IntStream.rangeClosed(0, 10).mapToObj(i -> new Employee((long) i)).collect(Collectors.toList()); } void printAllEmployee(CollegeManager collegeManager) { List<CollegeEmployee> collegeEmployees = collegeManager.getAllEmployee(); System.out.println(" branch employee "); collegeEmployees.stream().forEach(e -> System.out.println(e.getId())); List<Employee> employees = getAllEmployee(); System.out.println(" School headquarters staff "); employees.stream().forEach(s -> System.out.println(s.getId())); } } class CollegeManager { public List<CollegeEmployee> getAllEmployee() { return IntStream.rangeClosed(0, 10).mapToObj(i -> new CollegeEmployee((long) i)).collect(Collectors.toList()); }}Copy the code
- Traditional code analysis: The problem with the previous design is that in schoolManager, CollegeEmployee is not a direct friend of schoolManager, and Demeter’s law says you should avoid such indirect friend coupling in your classes.
- Code improvements: The problem with the previous design is that the CollegeEmployee class is not a direct friend of the SchoolManager class. According to Demeter’s law, such indirect friend coupling in classes should be avoided.
Improved code
class SchoolManager { public List<Employee> getAllEmployee() { return IntStream.rangeClosed(0, 10).mapToObj(i -> new Employee((long) i)).collect(Collectors.toList()); } void printAllEmployee(CollegeManager collegeManager) { collegeManager.printEmployee(); List<Employee> employees = getAllEmployee(); System.out.println(" School headquarters staff "); employees.stream().forEach(s -> System.out.println(s.getId())); } } class CollegeManager { public List<CollegeEmployee> getAllEmployee() { return IntStream.rangeClosed(0, 10).mapToObj(i -> new CollegeEmployee((long) i)).collect(Collectors.toList()); } void printEmployee() { List<CollegeEmployee> collegeEmployees = this.getAllEmployee(); System.out.println(" branch employee "); collegeEmployees.stream().forEach(e -> System.out.println(e.getId())); }}Copy the code
Demeter’s law notes and details
The core of Demeter’s law is to reduce the coupling between classes, but note that since each class reduces unnecessary dependencies, Demeter’s law only requires a reduction in the coupling between classes (objects), not a complete absence of dependencies.
Principle of composite reuse
The principle of composite reuse is to try to use aggregation and composition rather than inheritance.