Design pattern - Seven Principles

Posted by YappyDog on Mon, 06 Sep 2021 06:18:21 +0200

Seven principles of design mode

brief introduction

The principle of design pattern is actually the principle that programmers should abide by when programming, and it is also the basis of various design patterns (i.e. the basis for why design patterns are designed like this)

Seven principles commonly used in design patterns

  1. Single responsibility principle
  2. Interface isolation principle
  3. Dependency inversion (inversion) principle
  4. Richter substitution principle
  5. Opening and closing principle
  6. Dimitt's law
  7. Synthetic Reuse Principle

1. Principle of single responsibility

Basic introduction

Definition: there should be no more than one cause of class change. Generally speaking, a class is only responsible for one responsibility, and there should be only one reason for its change

explain

When it comes to the principle of single responsibility, many people will despise it. Because it's too simple. Even if experienced programmers have never read the design pattern or heard of the single responsibility principle, they will consciously abide by this important principle when designing software, because it is common sense. In software programming, no one wants to modify one function and cause other functions to fail. The way to avoid this problem is to follow the principle of single responsibility. Although the principle of single responsibility is so simple and considered common sense, even programs written by experienced programmers will have code that violates this principle. Why does this happen? Because of the proliferation of responsibilities. The so-called responsibility diffusion means that for some reason, responsibility P is divided into finer responsibilities P1 and P2.

The advantages of following a single responsibility are:
1. It can reduce the complexity of classes. A class is only responsible for one responsibility, and its logic must be much simpler than that of multiple responsibilities;

2. Improve the readability of the class and the maintainability of the system;

3. Risk reduction caused by change is inevitable. If the principle of single responsibility is well observed, the impact on other functions can be significantly reduced when modifying one function.

It should be noted that the principle of single responsibility is not unique to the idea of object-oriented programming. As long as it is modular programming, it needs to follow this important principle.

Original link: https://blog.csdn.net/u011288271/article/details/52497602

Notes and details of the principle of single post

  1. Reduce the complexity of classes. A class is responsible for only one responsibility

  2. Improve the readability and maintainability of classes

  3. Reduce risks caused by changes

  4. Generally, we should abide by the principle of single responsibility. Only when the logic is simple enough can we violate the principle of single responsibility at the code level; Only the number of methods in the class is small enough to maintain the principle of single responsibility at the method level

2. Interface isolation principle

Basic introduction

The client should not rely on interfaces it does not need, that is, the dependence of one class on another should be based on the smallest interface.

explain

The meaning of interface isolation principle is: establish a single interface, do not establish a huge and bulky interface, refine the interface as much as possible, and minimize the methods in the interface. In other words, we should establish a special interface for each class instead of trying to establish a huge interface for all classes that depend on it to call. In programming, it is more flexible to rely on several special interfaces than on a comprehensive interface. The interface is a "contract" set for the outside during design. By defining multiple interfaces in a decentralized manner, the proliferation of external changes can be prevented and the flexibility and maintainability of the system can be improved.

Speaking of this, many people will feel that the interface isolation principle is very similar to the single responsibility principle, but it is not. First, the principle of single responsibility originally focused on responsibility; The interface isolation principle focuses on the isolation of interface dependencies. Second, the single responsibility principle is mainly the constraint class, followed by the interface and method, which aims at the implementation and details of the program; The interface isolation principle mainly restricts the interface, mainly for the abstraction and the construction of the overall framework of the program.

When using the interface isolation principle to restrict the interface, pay attention to the following points:

  1. The interface should be as small as possible, but limited. It is a fact that refining the interface can improve the flexibility of program design, but if it is too small, it will cause too many interfaces and complicate the design. So be moderate.

  2. Customized services for classes that depend on interfaces are only exposed to the methods required by the calling class, and the methods it does not need are hidden. Only by focusing on providing customized services for a module can the minimum dependencies be established.

  3. Improve cohesion and reduce external interaction. Make the interface do the most with the least methods.

    The principle of interface isolation must be applied appropriately. Too large or too small interface design is not good. When designing interfaces, only by spending more time thinking and planning can we accurately practice this principle.

Original link: https://blog.csdn.net/u011288271/article/details/52497602

Improvement on the problems of traditional methods and the principle of using interface isolation

  1. Class a depends on class B through interface interface1, and class C depends on class D through interface interface1. If interface interface1 is not the smallest interface for class A and class C, class B and class D must implement methods they do not need

  1. The interface query is divided into several independent interfaces. Class A and class C establish dependencies with the interfaces they need. That is, the principle of interface isolation is adopted
  2. The methods in interface Interface1 are divided into three interfaces according to the actual situation. Class A and class C establish dependencies with the interfaces they need. That is, the principle of interface isolation is adopted

3. Reliance reversal principle

Basic introduction

Dependence Inversion Principle

  1. High level modules should not rely on low-level modules, and both should rely on their abstraction

2) Abstract should not rely on details, details should rely on abstraction

3) The central idea of dependency inversion is interface oriented programming

4) The dependency inversion principle is based on the design principle that abstract things are much more stable than the variability of details. Abstract based architecture is much more stable than detail based architecture. In java, abstraction refers to interfaces or abstract classes, and details are concrete implementation classes

  1. The purpose of using interfaces or abstract classes is to specify specifications without involving any specific operations, and hand over the additional task of showing details to their implementation classes.

explain

Dependency inversion actually means that no one should rely on anyone. In addition to the agreed interface, everyone can be flexible and free. Dependency inversion can be said to be the symbol of object-oriented design. It doesn't matter which language to write the program. If you consider how to program for abstract rather than for details, that is, all dependencies in the program terminate in abstract classes or interfaces, that's object-oriented design. On the contrary, that's procedural design. If the various components or classes of the design depend on each other, it is highly coupled and difficult to maintain and expand, which does not reflect the benefits of object-oriented.

The principle of relying on inversion is like a team with requirements, development and test groups. The development and test groups do their own work after facing the same requirements, rather than the test group making test cases according to the requirements understood by the development group, that is, the development group and test group work directly towards the requirements group, and everyone's purpose is the same, Ensure that the products are launched on time, and the requirements do not depend on development and testing.

The dependency inversion principle is based on the fact that abstract things are much more stable than the variability of details. An architecture based on abstraction is much more stable than an architecture based on detail. In java, abstraction refers to interfaces or abstract classes. Details are concrete implementation classes. The purpose of using interfaces or abstract classes is to formulate specifications and contracts without involving any specific operations, and hand over the task of showing details to their implementation classes.

The central idea of the dependency inversion principle is interface oriented programming. There are three ways to transfer dependencies. The above is interface transfer, and there are two other transfer methods: constructor method transfer and setter method transfer. I believe those who have used the Spring framework will not be unfamiliar with the transfer method of dependencies.
In actual programming, we generally need to do the following three points.

  1. Low level modules should have abstract classes or interfaces, or both
  2. The declaration type of a variable should be an abstract class or interface.
  3. Follow the Richter substitution principle when using inheritance.

In short, the dependency inversion principle requires us to understand interface oriented programming and dependency inversion when we understand interface oriented programming.

Original link: https://blog.csdn.net/u011288271/article/details/52497602

Three ways of implementation

Method 1: interface transfer implementation dependency

interface IOpenAndClose {
     void open(ITV itv);

}

interface ITV {
    void play();
}

class OpenAndClose implements IOpenAndClose {
    @Override
    public void open(ITV itv) {
        itv.play();
    }
}

class ChangHong implements ITV {
    @Override
    public void play() {
        System.out.println("Turn on Changhong TV");
    }
}

Method 2: construct method dependency transfer

interface ITV {
    void play();
}

interface IOpenAndClose {
    void open();
}

class ChangHong implements ITV {

    @Override
    public void play() {
        System.out.println("Turn on Changhong TV");
    }
}

class OpenAndClose implements IOpenAndClose {
    private ITV tv;

     public OpenAndClose (ITV tv) {
         this.tv = tv;
     }

    @Override
    public void open() {
        tv.play();
    }
}

Method 3: setter method transfer

interface ITV {
    void play();
}

interface IOpenAndClose {
    void open();
}

class ChangHong implements ITV {

    @Override
    public void play() {
        System.out.println("Turn on Changhong TV");
    }
}

class OpenAndClose implements IOpenAndClose {
    private ITV tv;

    public void setTv(ITV tv) {
        this.tv = tv;
    }
    @Override
    public void open() {
        tv.play();
    }
}

1) Low level modules should have abstract classes or interfaces, or both, for better program stability

2) The declaration type of variables shall be abstract classes or interfaces as far as possible, so that there is a buffer layer between our variable references and actual objects, which is conducive to program expansion and optimization

3) Follow the principle of Richter substitution when inheriting

4. Richter substitution principle

Thinking and explanation of inheritance in OO

  1. Inheritance contains such a meaning: all implemented methods in the parent class are actually setting specifications and contracts. Although it does not force all subclasses to follow these contracts, if subclasses arbitrarily modify these implemented methods, it will destroy the whole inheritance system.

2) Inheritance not only brings convenience to programming, but also brings disadvantages. For example, using inheritance will bring invasiveness to the program, reduce the portability of the program and increase the coupling between objects. If a class is inherited by other classes, all subclasses must be considered when the class needs to be modified, and all functions involving subclasses may fail after the parent class is modified

3) The question is: how to use inheritance correctly in programming? = > Richter substitution principle

Basic introduction

1) The Liskov Substitution Principle was proposed by a woman surnamed Li of MIT in 1988.

2) If for each object ol of type T1, there is an object o2 of type T2, so that all programs defined in T1 Р When all objects o1 are replaced with o2, the program Р If there is no change in the behavior of type T2, type T2 is a subtype of type T1. In other words, all references to the base class must be able to use the objects of its subclasses transparently.

3) When using inheritance, follow the Richter substitution principle and try not to override the methods of the parent class in the subclass

4) The Richter substitution principle tells us that inheritance actually enhances the coupling between the two classes. In appropriate cases, the problem can be solved through aggregation, composition and dependency.

explain

Description: if a software entity uses a parent class, it must be applicable to its child classes, and it cannot detect the difference between parent objects and child objects, that is, in the software, replace the parent class with its child classes, and the behavior of the program has not changed

Example: in biological classification, penguin is a kind of bird, but in the programming world, penguin can't inherit birds. In object-oriented design, subclasses have all non private behaviors and attributes of the parent class. Birds can fly, but penguins can't fly, so penguins can't inherit birds.

Only when the subclass can replace the parent class and the function of the software unit is not affected, the parent class can be reused, and the subclass can also add new behavior on the basis of the parent class. It is the Richter substitution principle that makes inheritance reuse possible. It is precisely because of the replaceability of subtypes that modules using parent types can be extended without modification. Otherwise, what else can we talk about opening extension and closing modification

Generally speaking, the Richter substitution principle is that subclasses can expand the functions of the parent class, but cannot change the original functions of the parent class. It contains the following four meanings:

1. Subclasses can implement the abstract methods of the parent class, but cannot override the non abstract methods of the parent class.

2. You can add your own unique methods in the subclass.

3. When the method of the subclass overloads the method of the parent class, the preconditions of the method (i.e. the formal parameters of the method) are more relaxed than the input parameters of the parent method.

4. When the method of the subclass implements the abstract method of the parent class, the post condition of the method (i.e. the return value of the method) is more stringent than that of the parent class.

It's incredible, because we will find that we often violate the Richter replacement principle in our programming, and the program still runs well. So everyone will have such a question, if I have to follow the Richter replacement principle, what will be the consequences?
The consequence is that the probability of problems in the code you write will greatly increase.

Original link: https://blog.csdn.net/u011288271/article/details/52497602

resolvent

1) We found an error in the subtraction function that was working normally. The reason is that class B inadvertently rewrites the method of the parent class, resulting in errors in the original function. In actual programming, we often complete new functions by rewriting the parent class. Although it is simple to write, the reusability of the whole inheritance system will be poor. Especially when polymorphism is frequent.

2) The common approach is: the original parent and child classes inherit a more popular base class, remove the original inheritance relationship, and replace it with dependency, aggregation, composition and other relationships

3) Improvement scheme

5. Opening and closing principle

Basic introduction

1) Open Closed Principle is the most basic and important design principle in programming

2) A software entity such as classes, modules and functions should be open to extensions (to providers). Close the modification (for the user). Build the framework with abstraction and extend the details with implementation.

3) When the software needs to change, try to realize the change by expanding the behavior of the software entity, rather than by modifying the existing code.

4) Other principles are followed in programming, and the purpose of using design patterns is to follow the opening and closing principles.

explain

The open closed principle means that when you design, you should always think about it. Try to make this class good enough. Don't modify it after it is written. If new requirements come, we'll finish adding some classes, and the original code can't move. This principle has two features, one is "open to extension" and the other is "closed to change". In the face of requirements, changes to the program are made by adding new code, not changing the existing code. This is the spirit of the "open closed principle"

It is absolutely impossible to modify and close the module. No matter how "closed" the module is, there will be some changes that cannot be closed. Since it is impossible to completely close the module, the designer must choose which change should be closed for the module he designs. He must first guess the most likely types of changes, and then construct abstractions to isolate those changes. When we first write code, we assume that changes will not occur. When changes occur, we create abstractions to isolate similar changes in the future.

We hope to know the possible changes soon after the development work starts. The longer we wait to find out the possible changes, the more difficult it is to create the correct abstraction. Open closed principle is the core of object-oriented design. Following this principle can bring great benefits claimed by object-oriented technology, that is, maintainability, scalability, reusability and good flexibility. Developers should abstract only those parts of the program that change frequently. However, it is not a good idea to deliberately abstract every part of the application. Rejecting immature abstraction is as important as the abstraction itself. The open closed principle can ensure the correctness of the previous code. Because the previous code has not been modified, it can ensure that developers focus on the newly extended code.

Original link: https://blog.csdn.net/u011288271/article/details/52497602

case

1) It violates the ocp principle of design pattern, that is, it is open to extension (provider) and closed to modification (user). That is, when we add new functions to the class, we try not to modify the code, or modify the code as little as possible

2) For example, if we want to add a new type of triangle, we need to make the following modifications. There are many modifications

package com.atguigu.principle.ocp;

public class Ocp {

	public static void main(String[] args) {
		//Use to see the existing problems
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(new Triangle());
	}

}

//This is a class for drawing [user]
class GraphicEditor {
	//Receive the Shape object and draw different shapes according to the type
	public void drawShape(Shape s) {
		if (s.m_type == 1)
			drawRectangle(s);
		else if (s.m_type == 2)
			drawCircle(s);
		else if (s.m_type == 3)
			drawTriangle(s);
	}

	//draw rectangle
	public void drawRectangle(Shape r) {
		System.out.println(" draw rectangle ");
	}

	//Draw circle
	public void drawCircle(Shape r) {
		System.out.println(" Draw circle ");
	}
	
	//Draw triangle
	public void drawTriangle(Shape r) {
		System.out.println(" Draw triangle ");
	}
}

//Shape class, base class
class Shape {
	int m_type;
}

class Rectangle extends Shape {
	Rectangle() {
		super.m_type = 1;
	}
}

class Circle extends Shape {
	Circle() {
		super.m_type = 2;
	}
}

//Add draw triangle
class Triangle extends Shape {
	Triangle() {
		super.m_type = 3;
	}
}


improvement

Make the created Shape class into an abstract class and provide an abstract draw method for the subclass to implement. In this way, when we have a new Shape type, we only need to let the new Shape class inherit the Shape and implement the draw method. The user's code does not need to be modified - > meets the opening and closing principle

package com.atguigu.principle.ocp.improve;

public class Ocp {

	public static void main(String[] args) {
		//Use to see the existing problems
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(new Triangle());
		graphicEditor.drawShape(new OtherGraphic());
	}

}

//This is a class for drawing [user]
class GraphicEditor {
	//Receive the Shape object and call the draw method
	public void drawShape(Shape s) {
		s.draw();
	}

	
}

//Shape class, base class
abstract class Shape {
	int m_type;
	
	public abstract void draw();//Abstract method
}

class Rectangle extends Shape {
	Rectangle() {
		super.m_type = 1;
	}

	@Override
	public void draw() {
		// TODO Auto-generated method stub
		System.out.println(" draw rectangle ");
	}
}

class Circle extends Shape {
	Circle() {
		super.m_type = 2;
	}
	@Override
	public void draw() {
		// TODO Auto-generated method stub
		System.out.println(" Draw circle ");
	}
}

//Add draw triangle
class Triangle extends Shape {
	Triangle() {
		super.m_type = 3;
	}
	@Override
	public void draw() {
		// TODO Auto-generated method stub
		System.out.println(" Draw triangle ");
	}
}

//Add a new drawing
class OtherGraphic extends Shape {
	OtherGraphic() {
		super.m_type = 4;
	}

	@Override
	public void draw() {
		// TODO Auto-generated method stub
		System.out.println(" Draw other graphics ");
	}
}

6. Dimitri's law

Basic introduction

1) One object should have minimal knowledge of other objects

2) The closer the relationship between classes, the greater the degree of coupling

3) The Demeter principle is also called the least known principle, that is, the less a class knows about the class it depends on, the better. In other words, no matter how complex the dependent class is, try to encapsulate the logic inside the class. Except for the public method provided, no information will be disclosed.

4) There is a simpler definition of Dimitri's Law: communicate only with direct friends

5) Direct friends: each object has a coupling relationship with other objects. As long as there is a coupling relationship between two objects, we say that the two objects are friends. There are many ways of coupling, such as dependency, association, combination, aggregation and so on. Among them, we call the classes that appear in member variables, method parameters and method return values as direct friends, while the classes that appear in local variables are not direct friends. In other words, unfamiliar classes should not appear inside the class in the form of local variables

import java.util.ArrayList;
import java.util.List;

//client
public class Demeter1 {

	public static void main(String[] args) {
		//Created a SchoolManager object
		SchoolManager schoolManager = new SchoolManager();
		//Output the employee id of the college and the employee information of the school headquarters
		schoolManager.printAllEmployee(new CollegeManager());

	}

}


//School headquarters staff
class Employee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//Staff of the College
class CollegeEmployee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//Management of the staff of the school of management
class CollegeManager {
	//All employees returning to the College
	public List<CollegeEmployee> getAllEmployee() {
		List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
		for (int i = 0; i < 10; i++) { //Here we added 10 employees to the list
			CollegeEmployee emp = new CollegeEmployee();
			emp.setId("College staff id= " + i);
			list.add(emp);
		}
		return list;
	}
}

//School management

//Analyze which direct friend classes of the SchoolManager class are Employee and CollegeManager
//College employee is not a direct friend, but a strange class, which violates Dimitri's law 
class SchoolManager {
	//Employees returning to the school headquarters
	public List<Employee> getAllEmployee() {
		List<Employee> list = new ArrayList<Employee>();
		
		for (int i = 0; i < 5; i++) { //Here we added five employees to the list
			Employee emp = new Employee();
			emp.setId("School headquarters staff id= " + i);
			list.add(emp);
		}
		return list;
	}

	//This method completes the output of school headquarters and college employee information (id)
	void printAllEmployee(CollegeManager sub) {
		
		//Analyze problems
		//1. The CollegeEmployee here is not a direct friend of SchoolManager
		//2. CollegeEmployee appears in SchoolManager as a local variable
		//3. Violation of Dimitri's law 
		
		//Get college employees
		List<CollegeEmployee> list1 = sub.getAllEmployee();
		System.out.println("------------College staff------------");
		for (CollegeEmployee e : list1) {
			System.out.println(e.getId());
		}
		//Get to the staff of the school headquarters
		List<Employee> list2 = this.getAllEmployee();
		System.out.println("------------School headquarters staff------------");
		for (Employee e : list2) {
			System.out.println(e.getId());
		}
	}
}

1) The problem with the previous design is that in SchoolManager, the CollegeEmployee class is not a direct friend (analysis) of the SchoolManager class

2) According to Demeter's law, such coupling of indirect friends in classes should be avoided

Improve the code according to Dimitri's law

import java.util.ArrayList;
import java.util.List;

//client
public class Demeter1 {

	public static void main(String[] args) {
		System.out.println("~~~Improvement of using Dimitri's law~~~");
		//Created a SchoolManager object
		SchoolManager schoolManager = new SchoolManager();
		//Output the employee id of the college and the employee information of the school headquarters
		schoolManager.printAllEmployee(new CollegeManager());

	}

}


//School headquarters staff
class Employee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//Staff of the College
class CollegeEmployee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//Management of the staff of the school of management
class CollegeManager {
	//All employees returning to the College
	public List<CollegeEmployee> getAllEmployee() {
		List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
		for (int i = 0; i < 10; i++) { //Here we added 10 employees to the list
			CollegeEmployee emp = new CollegeEmployee();
			emp.setId("College staff id= " + i);
			list.add(emp);
		}
		return list;
	}
	
	//Output information of College employees
	public void printEmployee() {
		//Get college employees
		List<CollegeEmployee> list1 = getAllEmployee();
		System.out.println("------------College staff------------");
		for (CollegeEmployee e : list1) {
			System.out.println(e.getId());
		}
	}
}

//School management

//Analyze which direct friend classes of the SchoolManager class are Employee and CollegeManager
//College employee is not a direct friend, but a strange class, which violates Dimitri's law 
class SchoolManager {
	//Employees returning to the school headquarters
	public List<Employee> getAllEmployee() {
		List<Employee> list = new ArrayList<Employee>();
		
		for (int i = 0; i < 5; i++) { //Here we added five employees to the list
			Employee emp = new Employee();
			emp.setId("School headquarters staff id= " + i);
			list.add(emp);
		}
		return list;
	}

	//This method completes the output of school headquarters and college employee information (id)
	void printAllEmployee(CollegeManager sub) {
		
		//Analyze problems
		//1. Encapsulate the employee method of the output college into the CollegeManager
		sub.printEmployee();
	
		//Get to the staff of the school headquarters
		List<Employee> list2 = this.getAllEmployee();
		System.out.println("------------School headquarters staff------------");
		for (Employee e : list2) {
			System.out.println(e.getId());
		}
	}
}

7. Composite Reuse Principle

Basic introduction

The principle is to try to use composition / aggregation instead of inheritance

Topics: Design Pattern