SRP The single responsibility principle represents the functional dependencies among the components of a module. From a software change perspective, there should be only one reason for a class to change; In layman’s terms, a class is responsible for only one responsibility. Assuming that a class P is responsible for two different responsibilities, P1 and P2, when the requirements of responsibility P1 change and class P needs to be modified, it may cause the function of responsibility P2, which previously worked well, to fail. Let’s imagine a scenario: there is an animal class that breathes air, and a class that describes the animal breathing scenario:
class Animal{
public void breathe(String animal){
System.out.println(animal + "Breathe the air"); }}public class Client{
public static void main(String[] args){
Animal animal = new Animal();
animal.breathe("Cow");
animal.breathe("Sheep");
animal.breathe("Pig"); }}Copy the code
Later, I found a new problem that not all animals need to breathe air, for example, fish need to breathe water. If the modification follows the single responsibility principle, then the Animal class needs to be divided into land and aquatic animals, and the code is as follows:
Class Terrestrial{public void breathe(String animal){system.out.println (animal + "breathe air "); }} class Aquatic{public void breathe(String animal){system.out.println (animal + "breathe water "); } } public class Client{ public static void main(String[] args){ Terrestrial terrestrial = new Terrestrial(); Terrestrial. Breathe (" cow "); Terrestrial. Breathe (" sheep "); Terrestrial. Breathe (" pig "); Aquatic aquatic = new Aquatic(); Aquatic. Breathe (" fish "); }}Copy the code
In practical work, if this modification is very expensive, in addition to the original Animal class decomposition into Terrestrial and Aquatic classes also need to modify the client, and directly modify the Animal class to achieve the purpose although it violates the single responsibility principle, but the cost is much less, The code is as follows:
Class Animal{public void breathe(String Animal){if(" fish ".equals(Animal)){system.out.println (Animal + "breathe water "); }else{system.out.println (animal + "breathe air "); } } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); Animal. Breathe (" cow "); Animal. Breathe (" sheep "); Animal. Breathe (" pig "); Animal. Breathe (" fish "); }}Copy the code
Can see, this simple changes obviously a lot of, but there is a hidden trouble, if one day have a need to add some kind of animals do not need to breath, then modify the breathe of Animal class methods, and to modify the original code may pose a risk to other related function, maybe one day you will find that the output becomes: This violates the single responsibility rule directly at the code level, which is the easiest change to make, but the most dangerous. There is another way to modify:
Class Animal{public void breathe(String Animal){system.out.println (Animal + "breathe "); } public void Breathe2 (String animal){system.out.println (animal + "breathe2 "); } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); Animal. Breathe (" cow "); Animal. Breathe (" sheep "); Animal. Breathe (" pig "); Animal. Breathe2 (" fish "); }}Copy the code
As you can see, this modification method does not change the original code, but adds a new method to the class, which violates the single responsibility principle, but it does not change the existing code, and does not affect the existing functionality. In actual programming, you need to decide which approach to use depending on the situation, and only if the logic is simple enough can you violate the single responsibility principle at the code level. Conclusion:
- SRP is a simple and intuitive principle, but it is difficult to apply it properly in the actual coding process. It needs to be applied according to the actual situation.
- The single responsibility principle can reduce the complexity of a class. The logic for a class to be responsible for only one responsibility is definitely simpler than for multiple responsibilities.
- Improve the readability of the code, improve the maintainability of the system.
The OCP open-close principle states that software entities (classes, modules, functions, etc.) should be extensible, but not modifiable. (Open for Extension, Close for modification) If a software can meet the OCP principle, it has two advantages:
- It can extend existing systems and provide new functions to meet new requirements, so the software has strong adaptability and flexibility.
- Existing modules, especially those important abstract modules, do not need to be modified, so the software has a strong stability and durability.
To take a simple example, here is a company that makes computers. Depending on the type of input, it makes different computers. The code is as follows:
interface Computer {} class Macbook implements Computer {} class Surface implements Computer {} class Factory { public Computer produceComputer(String type) { Computer c = null; if(type.equals("macbook")){ c = new Macbook(); }else if(type.equals("surface")){ c = new Surface(); } return c; }}Copy the code
The above code clearly violates the open-close principle. If you need to add a new computer product, modify produceComputer’s existing methods as follows:
interface Computer {} class Macbook implements Computer {} class Surface implements Computer {} interface Factory { public Computer produceComputer(); } class AppleFactory implements Factory { public Computer produceComputer() { return new Macbook(); } } class MSFactory implements Factory { public Computer produceComputer() { return new Surface(); }}Copy the code
The correct way is to abstract the Factory into an interface, and let specific factories (such as Apple factories and Microsoft factories) implement it and produce the corresponding products of their companies. This is written to facilitate expansion. If it is necessary to add a New Dell Factory to produce Dell computers, we only need to create a new computer class and a new Factory class. You don’t have to modify the code you’ve already written. Conclusion:
- OCP can have good scalability and maintainability.
- It is impossible to make all modules of a system comply with the OCP principle, what we can do is to try not to modify the already written code, the existing function, but to extend it.
The following is A common problem encountered in LSP programming: P1 is completed by class A. You need to extend P1 to P, where P consists of P1 and P2. If new function P is completed by subclass B of class A, subclass B may fail original function P1 while completing new function P2. Richter’s substitution principle tells us that when using inheritance, when class B inherits from class A, try not to change the expected behavior of the parent method, except by adding A new method to complete the new function P2. Richter’s substitution principle focuses on not affecting the original function, not overwriting the original method. Inheritance contains a layer of meaning: those who have achieved good method in the parent class (relative to the abstract method), are actually set a series of specifications and contract, although it is not compulsory for all the subclasses must conform to the contract, but if the subclasses for any modification, abstract methods of these products will damage to the entire inheritance system. Richter’s substitution principle is the expression of this meaning. For example, we need to do a function that subtracts two numbers:
class A{ public int func1(int a, int b){ return a-b; }}Copy the code
Later, we need to add a new feature: add the two numbers and then add them to 100, handled by class B. Class B needs to do two things:
- Two Numbers subtraction
- Add the two numbers and add 100
Since class A already implements the first function, class B only needs to complete the second function after inheriting from class A. The code is as follows:
class B extends A{ public int func1(int a, int b){ return a+b; } public int func2(int a, int b){ return func1(a,b)+100; }}Copy the code
We found that the subtraction function that used to run normally was wrong. The reason was that class B inadvertently overwrote the method of the parent class when naming the method, so that all the codes running the subtraction function called the method overwritten by class B, resulting in the error of the function that used to run normally. In practical programming, we often override methods of the parent class to accomplish new functions, which is easy to write, but often increases the risk of overwriting methods of the parent class. In plain English, the Richter substitution principle says that a subclass can extend the functionality of its parent class, but cannot change the functionality of its parent class. DIP definition: a high level module should not depend on a low level module. Both should be abstractions. Further, abstraction should not depend on details, details should depend on abstractions. For example, one day a product manager needs to add a new function that operates on a database, and the logic that typically encapsulates the database operations and processes the business is written by a different programmer. Encapsulating database operations can be considered a low-level module, while processing business logic can be considered a high-level module, so if processing business logic has to wait until the code that encapsulates database operations is written before it can be added, it will seriously slow down the project. The correct approach would be for the programmer handling the business logic to provide an abstract interface that encapsulates the database operations and hand it over to the programmer at the lower level, so that both sides can write it separately without affecting each other. At the heart of the dependency inversion principle is interface programming. Think of a scenario where a mother is telling a story to her child. Just give her a book and she can repeat the story. The code is as follows:
Class Book{public String getContent(){return "This is an interesting story "; }} class Mother{public void say(Book Book){system.out.println (" Mother begins to tell a story "); System.out.println(book.getContent()); } } public class Client{ public static void main(String[] args){ Mother mother = new Mother(); mother.say(new Book()); }}Copy the code
Suppose one day she was given a newspaper instead of a book and asked to tell the story, the code for the newspaper would read:
Class Newspaper{public String getContent(){return "this is an important news "; }}Copy the code
However, this Mother can not do it, she should only read books, this is too incredible, just to change the book into a newspaper, unexpectedly need to modify the Mother class to read, what if later need to change into a magazine? The reason is that the coupling between Mother and Book is so high that it has to be lowered. We could introduce an abstract interface, IReader books, and let books and newspapers implement this interface, so that no matter what kind of books are provided, the mother can read them. The code is as follows:
interface IReader{ public String getContent(); } class Newspaper implements IReader {public String getContent(){return "this is an important news "; }} class Book implements IReader{public String getContent(){return "This is a funny story "; }} class Mother{public void say(IReader reader){system.out.println (" Mother began to tell a story "); System.out.println(reader.getContent()); } } public class Client{ public static void main(String[] args){ Mother mother = new Mother(); mother.say(new Book()); mother.say(new Newspaper()); }}Copy the code
After this modification, any reading material provided in the future can be read by the mother as long as the IReader interface is implemented. In practice, the Mother class representing the high-level module takes care of the main business logic, and there is a high risk of introducing errors when it needs to be modified. Therefore, following the dependency reversal principle can reduce the coupling between classes, improve the stability of the system, and reduce the risk caused by modifying programs. The core of the dependency inversion principle is that we need to program to the interface, understand the interface programming, also understand the dependency inversion. Interface isolation Principle The ISP interface isolation principle emphasizes that clients should not rely on interfaces that they do not need; The dependency of one class on another should be based on the smallest interface. Let’s start with a picture:
As can be seen from the figure, class A depends on methods 1, 2, and 3 in interface I, and class B is A concrete implementation of class A. Class C depends on methods 1,4,5 in interface I, and class D is a concrete implementation of class C. For both classes B and D, there are methods that are not needed (the ones highlighted in red), but they must be implemented because interface I is implemented. In code:
interface I { public void method1(); public void method2(); public void method3(); public void method4(); public void method5(); } class A{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method2(); } public void depend3(I i){ i.method3(); }} class B implements I{// class B implements 1,2, 3; Public void method1() {system.out.println (" method1 of class B implements interface I "); } public void method2() {system.out.println (" class B implements method2 "); } public void method3() {system.out.println (" class B implements method3 "); } public void method4() {} public void method5() {} } class C{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method4(); } public void depend3(I i){ i.method5(); }} class D implements I{// class D implements I{// Public void method1() {system.out.println (" D implements method1 on interface I "); } public void method2() {} public void method3() {} public void method3() {} public void method4() {system.out.println (); } public void method5() {system.out.println (" class D implements method5 "); } } public class Client{ public static void main(String[] args){ A a = new A(); a.depend1(new B()); a.depend2(new B()); a.depend3(new B()); C c = new C(); c.depend1(new D()); c.depend2(new D()); c.depend3(new D()); }}Copy the code
As can be seen, if the interface definition is too bloated, as long as the methods appear in the interface, regardless of whether the dependent class needs the method, the implementation class must implement these methods, which does not comply with the interface isolation principle. To comply with the interface isolation principle, interface I must be split as shown in the following figure:
The code can be modified as follows:
interface I1 { public void method1(); } interface I2 { public void method2(); public void method3(); } interface I3 { public void method4(); public void method5(); } class A{ public void depend1(I1 i){ i.method1(); } public void depend2(I2 i){ i.method2(); } public void depend3(I2 i){ i.method3(); }} class B implements I1, I2{public void implements () {system.out.println (" class B implements I1 "); } public void method2() {system.out.println (" class B implements I2 "); } public void method3() {system.out.println (" class B implements I2 "); } } class C{ public void depend1(I1 i){ i.method1(); } public void depend2(I3 i){ i.method4(); } public void depend3(I3 i){ i.method5(); }} class D implements I1, I3{public void implements () {system.out.println (" class D implements I1 "); } public void method4() {system.out.println (" D implements I3 "); } public void method5() {system.out.println (" D implements I3 "); }}Copy the code
Conclusion: 3. The idea of interface isolation principle is to establish a single interface, refine the interface as much as possible, and contain as few methods as possible 4. However, there is a limit to everything. If the interface design is too small, there will be too many interfaces, which will complicate the design. So do it in moderation. Demeter’s Law (Least Know) LOD Demeter’s law, also known as the least know principle, states that one object should know the least about other objects. In layman’s terms, you only communicate with your immediate friends. First, let’s explain what a direct friend is: every object is coupled to other objects, and as long as there is a coupling between two objects, we say they are friends. There are many ways of coupling: dependency, association, composition, aggregation, etc. Classes that appear in member variables, method parameters, and method return values are direct friends, while classes that appear in local variables are not direct friends. That is, unfamiliar classes are best left inside the class as local variables. For the dependent class, no matter how complex the logic is, try to encapsulate the logic inside the class, provide public methods externally, and do not leak any information. For example, family visits to prisoners
- Family: Family members are only related to the prisoner, but do not know his fellow inmates
public class Family { public void visitPrisoner(Prisoners prisoners) { Inmates inmates = prisoners.helpEachOther(); imates.weAreFriend(); Public class Inmates {private Inmates = new Inmates() Public interior peachother () {system.out. Println (" the family says that you and your Inmates should helpEachOther..." ); return inmates; }} the Inmates are friends of the prison Inmates and do not know their family. The Inmates are public class void weAreFriend() {system.out.println ); Public class Prison {public static void main(String args[]) {Family Family = new Family(); family.visitPrisoner(new Prisoners()); }}Copy the code
The results will be found: family members say: you and your inmates should help each other… Cellmate said: we are cellmate…
Family and fellow Inmates were obviously don’t know, only allowed visiting prisoners, Family and prison rather than anyone can meet casually, Family and fellow Inmates here with communication is obviously contrary to the law of Demeter, because in the Inmates this class as a local variable in a method of the Family class, but they don’t know, can’t with direct communication, Demeter’s law tells us to correspond only with direct friends. So the above code can be changed to:
Public void visitPrisoner(Prisoners) {system.out.print (" Family say: "); public void visitPrisoner(Prisoners) {system.out.print (" Family say: "); prisoners.helpEachOther(); } } public class Prisoners { private Inmates inmates = new Inmates(); Public Inmates are supposed to helpEachOther. These Inmates are living for the first time. ); System.out.print(" prisoner says: "); inmates.weAreFriend(); return inmates; }} Public class Inmates {public void weAreFriend() {system.out.println (" we are Inmates... ); } } public class Prison { public static void main(String args[]) { Family family = new Family(); family.visitPrisoner(new Prisoners()); }}Copy the code
Family members said: prisoners and inmates should help each other… The prisoner said: We are fellow inmates…
This separated the family from the inmate, but also expressed the family’s hope that the inmate and the inmate could help each other. That is, two groups communicate through a third group, but there is no direct communication between family members and inmates. The PRINCIPLE of CRP composite/aggregate reuse is to use some existing objects in a new object and make them part of the new object. New objects reuse existing functionality by delegating to these objects. In object-oriented design, directly inheriting a base class breaks encapsulation because inheritance exposes the implementation details of the base class to subclasses; If the implementation of the base class changes, the implementation of the subclass must also change; Implementations inherited from base classes are static, cannot be changed at run time, and do not have sufficient flexibility. Therefore, the principle of combination/aggregation reuse is proposed, that is, in the actual development and design, try to use combination/aggregation, not class inheritance. To take a simple example, the employees in a company are divided into managers, workers, and salesmen. If drawn in UML diagram, it can be expressed as:
However, this violates the composite aggregation reuse principle, and inheritance exposes methods in the Employee class to subclasses. If you want to follow the combinatorial aggregate reuse principle, you can change it to:
This reduces the degree of coupling between classes, and changes to the Employee class have relatively little impact on other classes. Summary: 5. Overall, the combination/aggregation reuse principle tells us that combination or aggregation is better than inheritance. 6. Aggregate composition is a “black box” reuse because the contents of detail objects are not visible to the client.