Share with you the experience of "Zen of design patterns" (constantly updating ~ ~)

We knew the beauty of design patterns long ago. A good architecture is inseparable from the blessing of design patterns to cope with endless demand changes, so it's time to make up lessons~

Six principles of design patterns (reference book: "Zen of design patterns (Second Edition)")

Design pattern principle

1. Principle of unity

What is the principle of unity?

The principle of singleness is to try to make a class do only one thing (of course, this is a myth, which may kill programmers), but we can try our best to design like this. Please see the best example:

Best example

//We have a demand for a phone. Of course, it can also be a smartphone
public interface IPhone{
	//Make a call
	public void dial(String phoneNumber);
	//conversation
	public void chat(Object o);
	//Call over, hang up
	public void hangup();
}

The IPhone interface does not have only one responsibility. It includes two responsibilities: protocol management and data transmission. dial() and hangup() implement protocol management, which are respectively responsible for dialing and hanging up; chat() implements the transmission of data, converts what we say into analog signals or digital signals and transmits them to the other party, and then restores the signals transmitted by the other party to the language we understand. We can consider this problem in this way. Will the change of protocol connection lead to the change of this interface or implementation class? it will be! Will the change of data transmission (think, the telephone can not only talk, but also surf the Internet) cause the change of this interface or implementation class? it will be! That's simple. There are two reasons for class changes. Will these two responsibilities affect each other? Telephone dialing, as long as I can connect, no matter whether it is Telecom or Netcom protocol; Do you care what data is transmitted after the phone is connected? Through such analysis, we find that the IPhone interface on the class diagram contains two responsibilities, and the changes of these two responsibilities do not affect each other, so we should consider splitting it into two interfaces:

//One of them is responsible for making \ calls
public interface IConnectionManger{
	//Make a call
	public void dial(String phoneNumber);
	//Call over, hang up
	public void hangup();
}

//The other is responsible for data transmission
public interface IDataTransfer{
	// Transmission information
	public void DataTransfer(IConnectionManger cm)
}

Such a design is perfect. A class implements two interfaces and integrates two responsibilities into one class. You will think that this Phone has changed for two reasons. Yes, but don't forget that we are interface oriented programming. We publish interfaces rather than implementation classes. Moreover, if you really want to realize the single responsibility of the class, you must use the above composition mode, which will cause problems such as excessive coupling between classes, increasing the number of classes and artificially increasing the complexity of the design.

Through the above example, let's summarize the benefits of the single responsibility principle:

● the complexity of the class is reduced, and there is a clear definition of what responsibilities to implement;

● the readability is improved and the complexity is reduced. Of course, the readability is improved;

● maintainability and readability are improved, which is easier to maintain;

● the risk caused by the change is reduced, and the change is essential. If the single responsibility of the interface is well done, the modification of one interface will only affect the corresponding implementation class, but not other interfaces, which is very helpful to the expansibility and maintainability of the system.

2. Richter substitution principle

What is the Richter substitution principle?

First of all, Java is a single inheritance rule, that is, a subclass can only inherit a parent class with extensions (the parent has only one principle). Is this more in line with the principle. What is the principle of substitution? It has two definitions:

● the first definition of object for all, The behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T

● the second definition: Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it (all references to the base class must be able to use the objects of its subclasses transparently.)

Generally speaking, as long as the parent class can appear, the child class can appear, and replacing it with a child class will not produce any errors or exceptions. Users may not need to know whether it is a parent class or a child class at all. However, the reverse is not possible. Where there are subclasses, the parent class may not be able to adapt.

Best example

Now let's meet the need of a soldier to shoot and kill the enemy. What should we do?

//1. Abstract class of guns
public abstract class AbstractGun {
     //What are guns for? Kill the enemy!
     public abstract void shoot();
}
//Implementation of pistol, rifle and machine gun
//1-1 pistol
public class Handgun extends AbstractGun { 
     //The pistol is characterized by convenient carrying and short range
     @Override
     public void shoot() {
             System.out.println("Pistol shooting...");
     }
}
//1-2 rifle
public class Rifle extends AbstractGun{ 
     //The rifle is characterized by long range and great power
     @Override
     public void shoot(){
             System.out.println("Rifle shooting...");
     }
}
//1-3 machine gun
public class MachineGun extends AbstractGun{   
	@Override 
     public void shoot(){
             System.out.println("Machine gun fire...");
     }
}

//With guns, there should also be soldiers who can use these guns. The realization class of soldiers:
public class Soldier {
     //Define soldiers' guns
     private AbstractGun gun;
     //Give the soldier a gun
     public void setGun(AbstractGun _gun){
             this.gun = _gun; 
     }
     public void killEnemy(){
             System.out.println("The soldiers began to kill the enemy...");
             gun.shoot();
     }
}

//To realize the scenario, now let's start shooting soldiers:
public class Client {
     public static void main(String[] args) {
             //There are three hair soldiers
             Soldier sanMao = new Soldier();
             //Give three cents a gun
             sanMao.setGun(new Rifle());
             sanMao.killEnemy();
     }
}

**There are people, guns and scenes. The operation results are as follows:**

The soldiers began to kill the enemy...
Rifle shooting...

Now there is a new demand. The simulation gun appears. Let's realize it

//toy gun
public class ToyGun extends AbstractGun {
     //Toy guns cannot be fired, but the compiler requires this method. What should I do? Make up one!
     @Override
     public void shoot() {
             //If the toy gun cannot shoot, this method will not be realized
     }
}
//Scene class
public class Client {      
     public static void main(String[] args) {
             //There are three hair soldiers
             Soldier sanMao = new Soldier();
             sanMao.setGun(new ToyGun());
             sanMao.killEnemy();
     }
}

It's broken. Soldiers come to kill the enemy with toy guns. They can't shoot bullets! If this happens in CS games, you can wait to be shot in the head and watch yourself fall to the ground sadly. In this case, we find that there is a problem with the business call class, and the normal business logic can no longer run. What should we do? Easy to handle, there are two solutions:

● add instanceof judgment in Soldier class. If it's a toy gun, you don't have to kill the enemy. This method can solve the problem, but you should know that for every class added in the program, all classes related to the parent class must be modified. Do you think it is feasible? If this problem occurs in your product, because such a Bug is corrected, it requires all classes related to this parent class to add a judgment, and the customer must jump up and fight with you! Do you still want customers to be loyal to you? Obviously, the plan was rejected.

● ToyGun breaks away from inheritance and establishes an independent parent class. In order to realize code reuse, it can establish an association delegation relationship with AbastractGun:
In this way, the problem that toy guns cannot kill people is solved.
So what is the Richter substitution principle?? Let's move on:
At present, there is a new demand. A sniper needs a gun. Let's realize it

// Sniper!
public class AUG extends Rifle {   
	 //Sniper guns carry an accurate telescope
	     public void zoomOut(){
	             System.out.println("Look at the enemy through a telescope...");
	     }
	@Override 
     public void shoot(){
             System.out.println("long-range firing~~~");
     }
}

//There's a sniper gun, there's a sniper
public class Snipper {     
     public void killEnemy(AUG aug){
             //First look at the situation of the enemy. Don't kill the enemy. You'll be killed yourself
             aug.zoomOut();
             //Start shooting
             aug.shoot();
     }
}

According to the Richter substitution principle, the subclass can appear where the parent class appears. Can it be reversed? Give the sniper a rifle, Er ~ ~ ~ it seems that the range can't reach.....

When overriding or implementing the method of the parent class, the output can be reduced
What does this mean? The return value of a method of the parent class is a type T, and the return value of the same method (overload or override) of the subclass is S, so the Richter substitution principle requires that S must be less than or equal to t, that is, either S and T are of the same type, or S is a subclass of T. why? In two cases, if it is overridden, the input parameters of the method with the same name of the parent class and the child class are the same, and the range value S of the two methods is less than or equal to T. This is the requirement of overriding, which is the top priority. It is natural for the child class to override the method of the parent class. If it is overloaded, the input parameter type or quantity of the method is required to be different. Under the requirements of the Richter substitution principle, the input parameter of the subclass is wider than or equal to the input parameter of the parent class, that is, the method you write will not be called. Refer to the preconditions mentioned above.

The purpose of adopting Richter replacement principle is to enhance the robustness of the program, and maintain very good compatibility when upgrading the version. Even if subclasses are added, the original subclasses can continue to run. In the actual project, each subclass corresponds to different business meanings. It is perfect to use the parent class as a parameter to pass different subclasses to complete different business logic!

Keywords: Java

Added by Redneckheaven on Wed, 09 Feb 2022 21:11:03 +0200