Six principles of design mode

February 26, 2019 19:41:21

Six principles of design mode

Why are there six principles

It is said that "without rules, there is no square". Only with "rules" can we draw a "circle". What "circle" should be drawn according to the six principles of design mode?

Here we should start with object-oriented programming. From process-oriented programming to object-oriented programming is a big step in software design. Encapsulation, inheritance and polymorphism are the three characteristics of object-oriented. Originally, these are the benefits of object-oriented, but once abused, they will have a bad taste.

For example, encapsulation hides the attributes and implementation details of objects. I thought that before MVC was promoted, doGet and doPost methods in a servlet completed everything, such as business logic, data persistence and page rendering. In this way, when we need to modify business logic, we need to modify the servlet, and when we need to modify data persistence, we need to modify the servlet, Even page modifications modify the servlet. In this way, maintainability is very poor.

Due to the bad smell of code caused by abuse or incorrect, resulting in the low maintainability and reusability of the system, object-oriented needs to follow some principles to make the code better. For example, a servlet can change everything to MVC, and the classes of each layer do their own things and follow the principle of single responsibility.

In order to improve the maintainability, reusability, high cohesion and low coupling of the system, there are six principles. Because design pattern is the experience of object-oriented practice, these six principles are not only the six principles of object-oriented, but also the six principles of design pattern.

Six principles

Let's start with a picture and have a general feeling. In fact, it's simple and simple. It doesn't take ten minutes to memorize these six terms by rote, but it still takes a little effort to use them easily. This article is just on paper, talking about the definition, usage and benefits of the six principles.

Single responsibility principle (SRP)

Definition: There should never be more than one reason for a class to change.

Like the example I mentioned earlier, a servlet does everything, so there is more than one reason for servlet changes, so these things should be divided into different classes.

For example, I now want to implement a login function. The servlet code is as follows:

public class LoginServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. Get the data from the front end
        // 2. Connect database and query data
        // 3. Compare the data and get the results
        // 4. Encapsulate the result and return it to the front end
    }
}

After Applying MVC, the code is modified as follows:

public class LoginController {

    private LoginService loginService;

    public ModelAndView Login(HttpServletRequest req, HttpServletResponse resp) {
        // 1. Get the data from the front end
        loginService.login();
        // 4. Encapsulate the result and return it to the front end
        return null;
    }
}

public class LoginService {

    private UserDao userDao;

    public boolean login() {
        userDao.findOne();
        // 3. Compare the data and get the results
        return false;
    }
}

public class UserDao {

    public User findOne(){
        // 2. Connect database and query data
        return null;
    }
}

The figure is as follows:

In this way, the responsibilities are clear. If there is a need for change, just find the part related to the responsibilities and modify it. For example, to modify the comparison logic, modify the Service layer code; To modify the connection database, you can modify the Dao layer; To modify the contents of the returned page, you can modify the Controller layer.

Application scenario: specify the responsibilities of classes at the beginning of the project. If a class is found to have two or more responsibilities, break it into multiple classes. If it is late in the project, it is necessary to evaluate the cost of modification before refactoring. Don't let a class do too much.

Benefits: high cohesion, low coupling and increased code reusability.

Open closed principle (OCP)

Definition: Software entities like classes, modules and functions should be open for extension but closed for modifications.

From simple factory mode to factory method mode, the application scenario of opening and closing principle is perfectly explained. If you are interested, you can check what I wrote Simple factory model and Factory method model.

Implement operator operations with class objects:

Simple factory mode implementation:

public static IOperation createOperation(String op) {
    IOperation operation = null;

    if ("+".equals(op)) {
        operation = new AddOperationImpl();
    } else if ("-".equals(op)) {
        operation = new SubOperationImpl();
    } else if ("*".equals(op)) {
        operation = new MulOperationImpl();
    } else if ("/".equals(op)) {
        operation = new DivOperationImpl();
    }
    
    return operation;
}

This is the factory role implementation method in the simple factory mode to create the internal logic of all instances. When calling the method, select different implementation classes according to the operators passed in. However, if I want to add a power, I need to add else if structure, and the modification is not closed, so it does not comply with the opening and closing principle.

Factory method mode implementation:

// plus
// Create a specific factory
IOperationFactory operationFactory = new AddOperationFactoryImpl();
// Create specific products
IOperation operation = operationFactory.createOperation();
// Call the functions of specific products
int result = operation.getResult(a, b); 

If you need any operation, you can inherit the implementation class corresponding to the IOperationFactory implementation. When you use it, you only need to create a new implementation class where necessary. Without modifying the factory class and adding operations, the implementation class of the abstract factory class is added, which conforms to the opening and closing principle.

Application scenario: anywhere in the system

Advantages: the system has good stability and continuity while having adaptability and flexibility

Liskov Substitution Principle (LSP)

Definition: functions that use pointers or references to base classes must be able to use objects of derived classes while knowing it.

Why is it called the Richter substitution principle? The Richter substitution principle was proposed by Professor Barbara Liskov, the 2008 Turing prize winner and the first female doctor of Computer Science in the United States, and Professor Jeannette Wing of Carnegie Mellon University in 1994.

The Richter replacement principle tells us that if a base class object is replaced with its subclass object in the software, the program will not produce any errors and exceptions, and vice versa. If a software entity uses a subclass object, it may not be able to use the base class object. For example, if I like animals, I must like dogs, because dogs are a subclass of animals; But I like dogs. I can't conclude that I like animals because I don't like mice, even though it's also animals.

The above description is converted to the following code:

// animal
public interface Animal {
    public String getName();
}
// dog
public class Dog implements Animal{
    private String name = "dog";
    @Override
    public String getName() {
        return this.name;
    }
}
// mouse
public class Mouse implements Animal{
    private String name = "mouse";
    @Override
    public String getName() {
        return this.name;
    }
}
// Test class
public class ISPTest {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal mouse = new Mouse();
        iLoveAnimal(dog);
        iLoveAnimal(mouse);
//        iLoveDog(dog);
//        iLoveDog(mouse);
    }

    public static void iLoveAnimal(Animal animal) {
        System.out.println(animal.getName());
    }

    public static void iLoveDog(Dog dog) {
        System.out.println(dog.getName());
    }

}

Among them, the object parameters that iLoveAnimal(Animal animal) can pass are Dog and Mouse. Because Dog and Mouse are subclasses of Animal, they pass the compilation. iLoveDog(Dog dog) cannot use Mouse as a parameter. Although they are subclasses of Animal, they cannot pass compilation. In the compilation stage, the Java compiler will check whether a program complies with the Richter substitution principle, but the check of the Java compiler is limited. It is only a check in the pure syntax sense independent of the implementation. We should pay attention to following the Richter substitution principle in design.

Theoretically, the Richter substitution principle is one of the important ways to realize the opening and closing principle. Subclass objects can be used wherever the base class object is used. Therefore, the base class type is used to define the object as much as possible in the program. When the subclass type is determined at runtime, the extension can be realized when the subclass type is changed without modifying the existing code structure.

In practice, what we can do is:

  • Try to design base classes as abstract classes and interfaces
  • Subclasses must implement all methods declared in the parent class, and all methods of subclasses must be declared in the base class

The least knowledge principle (LKP) is also known as law of Demeter

Definition: only talk to your immediate friends.

The reason why it is also called Dimiter's law is that Dimiter's law came from a research project called "Demeter" at Northeast University in 1987.

According to Demeter's law, a method M of object O can only access the following types of objects:

  • 1. Current object O itself (this)
  • 2. Parameter object of M method (for example, object i in toString(Integer i))
  • 3. Current object O member object (object on which current object O directly depends)
  • 4. The object created in the M method

It is important that method M should not call the methods that return the object of these methods, that is, the methods that are returned by chain calls but do not return the object of its own object. Talk to your friends, not to your friends' friends, who are strangers to you.

Here is an example:

public class LawOfDelimterDemo {

    /**
     * This method has two violations of the principle of least knowledge or Dimitri's law.
     */
    public void process(Order o) {

        // This method call conforms to the Demeter's law because o is a parameter of the process method and a parameter of type 2
        Message msg = o.getMessage();

        // This method call violates Demeter's law because the msg object is used, which is obtained from the parameter object.
        // We should let Order normalize this Message, such as o.normalizeMessage(), instead of using the method of msg object
        msg.normalize();

        // This is also contrary to the Demeter's law. The method chain is used to replace the msg temporary variable mentioned above.
        o.getMessage().normalize();

        // constructor call 
        Instrument symbol = new Instrument();

        // as per rule 4, this method call is OK, because instance of Instrument is created locally.
        // This method call conforms to the Demeter's law, because the Instrument instance is created locally, which is the object of type 4 and the object created in the process method
        symbol.populate(); 
    }
}

Benefits: reduce the coupling of the system and increase the maintainability and adaptability of the system. Because they rely less on the internal structure of other objects, modifications to other objects modify their callers.

Disadvantages: it may increase the method of the object and cause other bug s.

Interface aggregation principle (ISP)

Definition: The dependency of one class to another one should depend on the smallest possible interface.

Example: first, there is a manager who is responsible for managing the workers. Secondly, there are two types of workers, one is average workers and the other is efficient workers. These workers all need lunch breaks to eat. Finally, there is another kind of robot working, but the robot doesn't need a lunch break.

Design implementation code:

interface IWorker {
    public void work();
    public void eat();
}

// General workers
class Worker implements IWorker {
    public void work() {
        // Normal operation
    }
    pubic void eat() {
        // Lunch break
    }
}

// Efficient worker
class SuperWorker implements IWorker {
    public void work() {
        // Efficient work
    }
    public void eat() {
        // Lunch break
    }
}

// robot
class Rebot implements IWorker {
    public woid work() {
        // work
    }
    public void eat() {
        // (the implementation code is empty and does nothing)
    }
}

class Manager {
    IWorker worker;

    public void setWorker(IWorker w) {
        worker = w;
    }
    public void manage() {
        worker.work();
        worker.eat();
    }

}

When the manager manages the workers and calls the interface eat method, the robot does nothing. We should make the interface smaller and split the IWorker interface.

// Work interface
interface IWorkable {
    public void work();
}
// Meal interface
interface IFeedable {
    public void eat();
}

// General workers
class Worker implements IWorkable, IFeedable {
    public void work() {
        // Normal operation
    }
    pubic void eat() {
        // Lunch break
    }
}

// Efficient worker
class SuperWorker implements IWorkable, IFeedable {
    public void work() {
        // Efficient work
    }
    public void eat() {
        // Lunch break
    }
}

// robot
class Rebot implements IWorkable {
    public woid work() {
        // work
    }
}

class Manager {
    IWorkable worker;
    IFeedable feed;

    public void setWorker(IWorkable w) {
        worker = w;
    }
    public void setfeed(IFeedable f) {
        feed = f;
    }
    public void manageWork() {
        worker.work();
    }
    public void manageFeed() {
        feed.eat();
    }

}

The IWorker interface is divided into IWorkable interface and IFeedable interface, and the interaction between Manager class and worker class depends on a relatively small interface as much as possible.

When using the interface isolation principle, we need to pay attention to controlling the granularity of the interface. The interface cannot be too small. If it is too small, it will lead to the overflow of interfaces in the system, which is not conducive to maintenance; The interface should not be too large. Too large an interface will violate the interface isolation principle, with poor flexibility and inconvenient use. Generally speaking, interfaces only contain methods customized for a certain type of users, and customers should not be forced to rely on methods they do not use.

Dependency Inversion Principle (DIP)

Definition: high level modules should not depend on low-level modules, they should all rely on abstraction. Abstractions should not depend on details. Details should depend on abstractions (high level modules should not depend on upon low level modules. Both should depend on upon abstractions. Abstractions should not depend on details. Details should depend on upon abstractions.).

Try to face interface programming rather than implementation programming.

Example: you are a director now. You want to make a film and are going to find Andy Lau as the protagonist. In the film, Andy Lau is a policeman who can catch prisoners.

// Lau Andy
class LiuDeHua {
    public LiuDeHua(){}
    public void catchPrisoner(){}
}

// script
class Play {
    LiuDeHua liuDeHua = new LiuDeHua();
    liuDeHua.catchPrisoner();
}

But because Huazai couldn't come on schedule, he went to Gu Tianle.

// Gu Tianle
class GuTianLe {
    public GuTianLe(){}
    public void catchPrisoner(){}
}

// script
class Play {
    GuTianLe guTianLe = new GuTianLe();
    guTianLe.catchPrisoner();
}

Gu Zi said he would donate money to build a school. He didn't have time to come. So he said he wanted to find Liu Qingyun. The screenwriter was very tired

If the screenwriter only faces interface programming, it will be like this:

// police
interface Police {
    public void catchPrisoner();
}

// script
class Play {
    private Police police;
    public Play(Police p) {
        police = p;
    }
    police.catchPrisoner();
}

In this way, no matter who comes, you only need to implement the Police interface to shoot according to the script.

When implementing the dependency inversion principle, we need to program for the abstract layer, and inject the objects of specific classes into other objects through dependency injection (DI). Dependency injection refers to injecting the dependent objects through abstraction when an object wants to have a dependency relationship with other objects.

There are three common injection methods: structure injection, Setter injection and interface injection. Construction injection refers to passing in objects of specific classes through constructors, setting injection refers to passing in objects of specific classes through Setter methods, and interface injection refers to passing in objects of specific classes through business methods declared in interfaces. These methods use abstract types when they are defined. At run time, specific types of objects are passed in, and the parent object is overwritten by the child object.

In most cases, the opening and closing principle, the Richter substitution principle and the dependence inversion principle will appear at the same time. The opening and closing principle is the goal, the Richter substitution principle is the foundation and the dependence inversion principle is the means. They complement each other and have the same goal, but they stand from different angles when analyzing problems.

reference resources

Law of Demeter in Java - Principle of least Knowledge

Dependency inversion principle of object-oriented design principles

March 22, 2019 14:31:03

Added by Ben5on on Mon, 29 Nov 2021 16:22:07 +0200