Crusade against design patterns-basic principles

This article is a note made when learning design patterns, the original video link:

https://www.bilibili.com/video/BV1G4411c7N4

table of Contents

Design Patterns

UML

UML class diagram

The composition of the class

Relationship between classes

Dependence

Generalization

Implementation

Association

Aggregation and composition

Seven principles of design patterns

Single responsibility principle

Interface isolation principle

Dependence reversal principle

Richter's Substitution Principle

Principle of opening and closing

Dimit's Law

Synthetic reuse principle


Design Patterns

1. What is a design pattern?

Design patterns are solutions to various problems that are common (recurring) in software design.

2. Why use design patterns?

Using design patterns, programs can have better:

  • Code reusability: the code to achieve the same function does not need to be written repeatedly;
  • Readability: Enhance the standardization of programming, making it easy for other programmers to read;
  • Scalability: It is very convenient to add new functions to the original program;
  • Reliability: After adding a new function, it will not affect the original function;
  • Cohesion and coupling: that is, "high cohesion, low coupling".

The design pattern contains the essence of object-oriented.

 

The video uses a graphical method to explain the design pattern, and you should have a certain understanding of UML diagrams before learning.

If you have mastered UML diagrams, you can skip this part.

UML

UML (Unified Modeling Language) is a language tool for software analysis and design.

UML is essentially a set of symbol requirements, these symbols are used to describe the various elements (such as classes, interfaces) in the software model and the relationships between them (such as generalization, implementation).

UML diagram classification:

Static structure diagram: class diagram, object diagram, package diagram, component diagram, deployment diagram.

Dynamic behavior diagram: interaction diagram, state diagram, activity diagram.

Among them, the class diagram is used to describe the relationship between the class and the class. When explaining the design pattern, the class diagram is mainly used.

UML class diagram

UML class diagram for the class described system itself, the composition and kinds of static relationships between classes .

The composition of the class

We draw the following class as a UML diagram:

public class Person{
	private Integer id;
	private String name;

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
}

UML class diagram:

As you can see, the information contained in the class in the UML class diagram includes: class name (Person), member variables (id, name), and member methods (setName, getName).

Among them, member variable information includes member variable name and member variable type, separated by ":"; member method information includes member method name, parameter list, and return value type.

Relationship between classes

The relationship between classes includes: dependency, generalization, realization, association, aggregation, and combination.

rely:

Generalization:

achieve:

Association:

polymerization:

combination:

Dependence

As long as another class is used in one class, there is a dependency between the two classes.

The ways to use one class A in another class B are:

  • B as a member attribute of A
  • B as the parameter received by the method in A
  • B as the return type of the method in A
  • A uses B in the method

Take the following PersonServiceBean class as an example:

public class PersonServiceBean {
	private PersonDao personDao;// PersonDao类作为成员属性

	public void save(Person person) {// Person类作为方法的接收参数
	}

	public IDCard getIDCard(Integer personid) {// IDCard类作为方法的返回类型
		return null;
	}

	public void modify() {
		Department department = new Department();// Department类在方法中被用到
	}
}

class PersonDao {
}

class IDCard {
}

class Person {
}

class Department {
}

The above four forms all constitute a dependency relationship.

To express with UML diagram is:

Three ways of dependency transfer: interface, constructor, setter

Through the interface:

interface IOpenAndClose {
	public void open(ITV tv);
}

interface ITV {
	public void play();
}

class OpenAndClose implements IOpenAndClose {
	public void open(ITV tv) {
		tv.play();
	}
}

class ChangHong implements ITV {
	@Override
	public void play() {
		System.out.println("打开长虹电视机");
	}
}

public class Test {
	public static void main(String[] args) {
		ChangHong changHong = new ChangHong();
		OpenAndClose openAndClose = new OpenAndClose();
		openAndClose.open(changHong);
	}
}

Through the construction method:

interface IOpenAndClose {
	public void open();
}

interface ITV {
	public void play();
}

class OpenAndClose implements IOpenAndClose {
	public ITV tv;

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

	public void open() {
		this.tv.play();
	}
}

class ChangHong implements ITV {
	@Override
	public void play() {
		System.out.println("打开长虹电视机");
	}
}

public class Test {
	public static void main(String[] args) {
		ChangHong changHong = new ChangHong();
		OpenAndClose openAndClose = new OpenAndClose(changHong);
		openAndClose.open();
	}
}

Through the setter method:

interface IOpenAndClose {
	public void open();

	public void setTv(ITV tv);
}

interface ITV {
	public void play();
}

class OpenAndClose implements IOpenAndClose {
	public ITV tv;

	public void setTv(ITV tv) {
		this.tv = tv;
	}

	public void open() {
		this.tv.play();
	}
}

class ChangHong implements ITV {
	@Override
	public void play() {
		System.out.println("打开长虹电视机");
	}
}

public class Test {
	public static void main(String[] args) {
		ChangHong changHong = new ChangHong();
		OpenAndClose openAndClose = new OpenAndClose();
		openAndClose.setTv(changHong);
		openAndClose.open();
	}
}

Output:

Turn on Changhong TV

Generalization

The generalization relationship is actually the inheritance in object-oriented, and it is essentially a special case of the dependency relationship.

If class A inherits class B, we say that there is a generalization relationship between class A and class B.

Take the following DaoSupport and PersonServiceBean classes as examples:

public abstract class DaoSupport {
	public void save(Object entity) {
	}

	public void delete(Object id) {
	}
}

class PersonServiceBean extends DaoSupport {
}

The UML diagram is: (the subclass points to the parent class)

Implementation

The realization relationship is actually the realization in object-oriented, and it is also a special case of the dependency relationship.

If class A implements interface B, we say that there is an implementation relationship between class A and interface B.

Take the following PersonService interface and PersonServiceBean class as examples:

public interface PersonService {
	public void delete(Integer id);
}

class PersonServiceBean implements PersonService {
	public void delete(Integer id) {
		System.out.println("delete");
	}
}

Expressed in UML diagrams is: (class points to interface)

Association

Association relationship is a strong dependency between class and class, or class and interface.

The association relationship is navigational and can be a two-way relationship or a one-way relationship.

The relationship can be one-to-one, one-to-many, and many-to-many.

Take one-way one-to-one relationship and two-way one-to-one relationship as examples:

One-way one-to-one relationship:

public class Person {
	private IDCard card;
}

class IDCard {
}

UML graphics:

Two-way one-to-one relationship:

public class Person {
	private IDCard card;
}

class IDCard {
	private Person person;
}

UML graphics:

Aggregation and composition

Aggregation relationship:

The aggregation relationship represents the relationship between the whole and the part, and the aggregation relationship thinks that the part can be separated from the whole.

If the part cannot be separated from the whole, it is a combination relationship.

The aggregation relationship is a special case of the association relationship, so it has the navigation and multiplicity of the association relationship.

Take a computer as an example. The computer is composed of keyboard, monitor, mouse and other parts. If we think that all parts of the computer can be separated from the computer, use code Means:

public class Computer {
	private Monitor monitor;
	private Mouse mouse;
}

class Monitor {
}

class Mouse {
}

The aggregation relationship is represented by UML diagrams:

Combination relationship:

The combination relationship also represents the relationship between the whole and the part, and the combination relationship considers that the part cannot be separated from the whole.

If we think that Monitor, Monse, and Computer are inseparable, the above aggregation relationship can be upgraded to a combination relationship.

public class Computer {
    private Monitor monitor = new Monitor();
    private Mouse mouse = new Mouse();
}

class Monitor {
}

class Mouse {
}

In this way, as long as the Computer object is created, the monitor and mouse will be created; similarly, as long as the Computer object is destroyed, the corresponding monitor and mouse will be destroyed together.

The combination relationship is represented by UML diagrams as follows:

In view of the fact that "can the monitor and mouse be separated from the computer" is a philosophical question, the following uses an example of a person-head-IDCard to compare the aggregation relationship and the combination relationship.

public class Person {
	private IDCard card;// 聚合关系,可以分离
	private Head head = new Head();// 组合关系,不可分离
}

class IDCard {
}

class Head {
}

To express with UML diagram is:

 

Seven principles of design patterns

Design pattern principles are the rules that programmers should abide by when programming using design patterns.

Question: Why is the xx mode designed like this?

Answer: Because of the xx design principle.

Design pattern principles include:

  • Single responsibility principle
  • Interface isolation principle
  • Dependence reversal principle
  • Richter's Substitution Principle
  • Principle of opening and closing
  • Dimit's Law
  • Synthetic reuse principle

Single responsibility principle

Single responsibility principle: A class should only be responsible for one responsibility.

If Class A is responsible for two different responsibilities: Responsibility 1, Responsibility 2, when the requirements of Responsibility 1 change and require a change to A, it may cause the execution of Responsibility 2 to be wrong. Class A can be decomposed into A1 and A2, which are responsible for responsibility 1 and responsibility 2 respectively.

The main purpose of following the single responsibility principle is to reduce the complexity of the class, improve the readability and maintainability of the class, and reduce the risk of business changes.

Interface isolation principle

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

Suppose there are five abstract methods defined in interface Interface1, class B and class D implement Interface1, class A depends on class B through interface Interface1, but only three methods of interface 1, 2, and 3 are used, and class C passes The interface Interface1 depends on the class D, but only the three methods 1, 4, and 5 in the interface are used.

If you design according to the following figure:

This violates the principle of interface isolation, because Interface1 is not the smallest interface for class A and class C, and class B and class D must implement methods they do not need.

In order to follow the principle of interface isolation, the interface Interface1 can be split into several independent interfaces, and class A and class C respectively establish a dependency relationship with the interfaces they need. As shown below:

Dependence reversal principle

Dependence reversal principle:

  • High-level modules should not rely on low-level modules, both should rely on their abstractions.
  • Abstraction should not depend on details, and details should depend on abstraction.
  • The central idea of ​​dependency inversion is interface-oriented programming.
  • Relying on the design concept of the principle of inversion: relative to the variability of details, abstract things are relatively stable. Architecture based on abstraction is much more stable than architecture based on details. In Java, abstraction refers to the interface or abstract class, and the details refer to the specific implementation class.
  • The purpose of using interfaces or abstract classes is to formulate specifications without involving any specific implementation, and the task of showing details is left to their implementation classes to complete.

The precautions for relying on the reversal principle are:

  • The underlying modules should have abstract classes or interfaces as much as possible.
  • The declaration type of the variable should be an abstract class or interface as much as possible, so that there is a buffer layer between the variable reference and the actual object, which is conducive to program expansion and optimization.
  • Follow the Richter substitution principle when inheriting.

Let's write a Person class that receives email messages without following the principle of dependency inversion:

public class Test {
	public static void main(String[] args) {
		Person person = new Person();
		person.receive(new Email());
	}
}

class Email {
	public String getInfo() {
		return "Email: Hello World!";
	}
}

class Person {
	public void receive(Email email) {
		System.out.println(email.getInfo());
	}
}

Output result:

Email: Hello World!

This is easier to understand.

Now we want to add a function to receive WeChat messages, we need to add a WeChat class, and add a method to receive WeChat messages in the Person class:

public class Test {
	public static void main(String[] args) {
		Person person = new Person();
		person.receive(new Email());
		person.receive(new WeChat());
	}
}

class Email {
	public String getInfo() {
		return "Email: Hello World!";
	}
}

class WeChat {//新增WeChat类
	public String getInfo() {
		return "WeChat: Hello World!";
	}
}

class Person {
	public void receive(Email email) {
		System.out.println(email.getInfo());
	}

	public void receive(WeChat weChat) {//新增接收WeChat消息的方法
		System.out.println(weChat.getInfo());
	}
}

Output result:

Email: Hello World!
WeChat: Hello World!

Of course, the requirement can be fulfilled by doing so, but every time the function of receiving a new message type is added, the Person class needs to be changed (violating the principle of opening and closing). Following the dependency inversion principle, we can add new message types without changing the Person class.

Create an interface IReceiver, and make each message type implement this interface:

public class Test {
	public static void main(String[] args) {
		Person person = new Person();
		person.receive(new Email());
		person.receive(new WeChat());
	}
}

interface IReceiver {//创建接口IReceiver
	String getInfo();
}

class Email implements IReceiver {//Email类实现IReceiver接口
	public String getInfo() {
		return "Email: Hello World!";
	}
}

class WeChat implements IReceiver {//新增的WeChat类实现IReceiver接口
	public String getInfo() {
		return "WeChat: Hello World!";
	}
}

class Person {
	public void receive(IReceiver receiver) {//参数改为IReceiver接口对象
		System.out.println(receiver.getInfo());
	}
}

Output result:

Email: Hello World!
WeChat: Hello World!

After that, every time the function of receiving a new message type is added, a class of the new message type is created and the IReceive interface is implemented. There is no need to change the Person class.

Richter's Substitution Principle

Richter substitution principle: If for each object o1 of type T1, there is an object o2 of type T2, so that all programs P defined in T1 will behave no behavior when all objects o1 are replaced by o2. Change, then type T2 is a subtype of type T1. In other words, all references to the base class must be able to transparently use the objects of its subclasses.

To put it more bluntly , try not to override the method of the parent class in the subclass.

Inheritance actually enhances the coupling of the two classes. Under appropriate circumstances, you can use dependency, aggregation, and composition to replace inheritance to solve the problem (synthetic reuse principle).

The usual approach is to remove the original inheritance relationship, and the original parent and child classes inherit a more popular base class, and use dependency, aggregation, and composition relationships instead.

For example, class B inherits class A:

class A {
	public int func1(int a, int b) {
		return a + b;
	}
}

class B extends A {
	public int func1(int a, int b) {
		return a - b;
	}
}

The func1 method in class A is rewritten.

Can be changed to:

class Base {

}

class A extends Base {
	public int func1(int a, int b) {
		return a + b;
	}
}

class B extends Base {
	private A a = new A();

	public int func1(int a, int b) {
		return a - b;
	}

	public int func2(int a, int b) {
		return this.a.func1(a, b);
	}
}

Calling func2 in class B is equivalent to calling func1 in class A.

Principle of opening and closing

The principle of opening and closing: a software entity (such as class, module, function) should be open to extension and closed to modification. Build the framework with abstraction, and expand the details with implementation.

It is open to providers and closed to users.

When the software needs to change, try to achieve the change by extending the behavior of the software entity, rather than by modifying the existing code to achieve the change.

Follow other principles in programming, and the purpose of using design patterns is to follow the principle of opening and closing.

Let's go back to the example given when we talked about the dependency inversion principle, where the People class is the user, and we should avoid modifying the People class when adding new functions, and we should extend the existing software entities to complete the requirements.

public class Test {
	public static void main(String[] args) {
		Person person = new Person();
		person.receive(new Email());
		person.receive(new WeChat());
	}
}

interface IReceiver {
	String getInfo();
}

class Email implements IReceiver {
	public String getInfo() {
		return "Email: Hello World!";
	}
}

class WeChat implements IReceiver {
	public String getInfo() {
		return "WeChat: Hello World!";
	}
}

class Person {
	public void receive(IReceiver receiver) {
		System.out.println(receiver.getInfo());
	}
}

Dimit's Law

Dimit's Law: An object should have the least knowledge of other objects.

Dimit’s Rule is also called the Least Knowing Principle, that is, the less a class knows about the classes it depends on, the better. No matter how complex the dependent classes are, try to encapsulate the logic inside the class, except for the public methods provided. Leak any information to the outside world.

Following Dimit's law is to reduce the coupling between classes.

We only need to remember one thing about Dimit's rule: unfamiliar classes should not appear inside the class in the form of local variables.

The so-called unfamiliar classes are classes that have not appeared in member variables, method parameters, and method return values.

Synthetic reuse principle

The principle of composition reuse: try to use composition/aggregation instead of inheritance.

If we want class B to use the methods in class A, using inheritance will enhance the coupling between A and B:

A and B can be improved into a dependency relationship:

Or change it to an aggregation relationship:

Or change to a combination relationship:

 

Come on!

Guess you like

Origin blog.csdn.net/qq_42082161/article/details/111299870