Writing in the front

  • Take notes on learning design patterns
  • Improve flexibility in the use of design patterns

Learning to address

https://www.bilibili.com/vide…

https://www.bilibili.com/vide…

Refer to the article

http://c.biancheng.net/view/1…

Project source https://gitee.com/zhuang-kang/DesignPattern

3. Software design principles

In software development, in order to improve the maintainability and reusability of software system, increase the expansibility and flexibility of software, programmers should try to develop programs according to six principles, so as to improve the efficiency of software development, save software development cost and maintenance cost.

3.1 Open and Close Principle

Open for extensions, closed for modifications. When the program needs to be expanded, the original code can not be modified to achieve a hot plug effect. In short, it is to make the program extensible, easy to maintain and upgrade.

To do this, we need to use interfaces and abstract classes.

Because the abstraction is flexible and adaptable, as long as the abstraction is reasonable, it can basically maintain the stability of the software architecture. The mutable details in the software can be extended from the abstractionist implementation classes, and when the software needs to change, it can be extended by simply deriving another implementation class based on the requirements.

3.2 Richter Substitution Principle

Richter substitution principle is one of the basic principles of object-oriented design.

Richter substitution principle: any base class can appear, subclass must appear. The subclass can extend the function of the superclass, but it cannot change the original function of the superclass. In other words, when a subclass inherits from a superclass, try not to override the methods of the superclass except to add new methods to complete the new function.

If the new function is completed by rewriting the method of the parent class, although it is simple to write, the reusability of the whole inheritance system will be relatively poor, especially when the polymorphic is used frequently, the probability of program running error will be very large.

Wrong demonstration of the Richter substitution principle

package com.zhuang.principle.liskov; /** * @ClassName Liskov * @Description * @Date 2021/3/15 13:58 * @Created by Dell */ public class Liskov { public static void main(String[] args) { A a = new A(); System. The out. Println (" 11-3 = "+ a.f un1 (11, 3)); System. The out. Println (" 11-8 = "+ a.f un1 (11, 8)); System.out.println("==================="); B b = new B(); System. The out. Println (" 11-3 = "+ b. un1 (11, 3)); System. The out. Println (" 1-8 = "+ b. un1 (1, 8)); System. The out. Println (" 11 + 3 + 9 = "+ b. un2 (11, 3)); }} class A{public int fun1(int num1,int num2){return num1-num2; }}} class B extends A{@Override public int fun1(int A, int B) {return A + B; } public int fun2(int a, int b) { return fun1(a,b)+9; }}

Correct demonstration of the Richter substitution principle

package com.zhuang.principle.liskov; /** * @className Liskov2 * @description * @date 2021/3/15 14:13 * @created by dell */ public class Liskov2 { public static void main(String[] args) { Base base = new Base(); Base. The add (5, 6); Base. Sub (6, 2); Sub sub = new Sub(); Sub. The mul (5, 6); Sub. Div (10, 2); } class Base {public void add(int a, int b) {System.out.println(a +" +" + b +" =" + (a + b)); Public void sub(int a, int b) {System.out.println(a + "-" + b + "=" + (a - b));} public void sub(int a, int b) {System.out.println(a + "-" + b + "=" + (a - b)); } public void mul(int a) public void mul(int a) public void mul(int a) int b) { System.out.println(a + "*" + b + "=" + (a * b)); Public void div(int a, int b) {System.out.println(a + "/" + b + "=" + (a/b));} public void div(int a, int b) {System.out.println(a + "/" + b + "=" + (a/b)); }}

3.3 Rely on the inversion principle

High-level modules should not depend on low-level modules, both should depend on their abstractions; Abstractions should not depend on details, details should depend on abstractions. Simply put, it requires that abstractions be programmed, not implementations, thus reducing the coupling between the client and the implementation module.

Error demonstration of reliance on the inversion principle

package com.zhuang.principle.inversion; /** * @ClassName dependenceInversion1 * @Description * @Date 2021/3/15 13:20 * @Created by Dell */ public class DependenceInversion1 { public static void main(String[] args) { Person person = new Person(); person.receive(new Email()); person.receive(new WeiXin()); Public String getInfo(); public String getInfo(); public String getInfo(); } class WeiXin implements iReceiver {@Override public String getInfo() {return "send WeChat..." ; }} class Email implements iReceiver {@Override public String getInfo() {return "...";} public String getInfo() {return "..."; ; Class Person{public void receive(iReceiver receiver){System.out.println(receiver.getInfo()); public void receive(iReceiver receiver){System.out.println(receiver. }}

Correct demonstration of reliance on the inversion principle

package com.zhuang.principle.inversion; /** * @ClassName dependenceInversion2 * @Description * @Date 2021/3/15 13:27 * @Created by Dell */ public class DependenceInversion2 { public static void main(String[] args) { Client client = new Client(); client.receive(new Emailiml()); client.receive(new WXimpl()); } } interface IReceive{ public void printInfo(Integer uid); } class WXImpl implements IReceive {@Override public void printInfo(Integer UID) {System.out.println() + UID);} class WXImpl implements IReceive {@Override public void printInfo(Integer UID); } } class Emailiml implements IReceive { @Override public void printInfo(Integer uid) { System.out.println(" Send mail message "+uid); } } class Client{ public void receive(IReceive receive){ receive.printInfo(12345); }}

Object-oriented development is a good solution to this problem, in general, the probability of abstraction change is very small, let the user program depend on the abstraction, implementation details also depend on the abstraction. Even if the implementation details are constantly changing, the client doesn’t need to change as long as the abstractions remain the same. This greatly reduces the coupling between the client program and the implementation details.

3.4 Principle of interface isolation

The client should not be forced to rely on methods it does not use; A class’s dependency on another class should be based on the smallest interface.

Interface isolation principle

package com.zhuang.principle.segregation; /** * @ClassName Sergregation * @Description * @Date 2021/3/15 13:02 * @Created by Dell */ public class Sergregation { public static void main(String[] args) { C c = new C(); c.depend1(new A()); c.depend2(new A()); C. depend3(new A()); C (new A()); System.out.println("======================="); D d = new D(); d.depend1(new B()); d.depend4(new B()); // class D depends on class B via the interface d.depend5(new B()); } } interface interface1{ void operation1(); } interface interface2{ void operation2(); void operation3(); } interface interface3{ void operation4(); void operation5(); } class A implements interface1,interface2{ @Override public void operation1() { System.out.println("A Implements the operation1..." ); } public void operation2() {System.out.println("A implements operation2......");} public void operation2() {System.out.println(); ); } public void operation3() {System.out.println("A implements operation3......");} public void operation3() {System.out.println(); ); } } class B implements interface1,interface3{ @Override public void operation1() { System.out.println("B Implements the operation1..." ); } @Override public void operation4() {System.out.println("B implements operation4.....") {public void operation4() {System.out.println(); ); } @Override public void operation5() {System.out.println("B implements operation5.....") {Override public void operation5() {System.out.println(); ); Class C{public void depend1(interface1 I){i.opperation1 (); } public void depend2(interface2 i){ i.operation2(); } public void depend3(interface2 i){ i.operation3(); Class D{public void depend1(interface1 I){i.opperation1 (); } public void depend4(interface3 i){ i.operation4(); } public void depend5(interface3 i){ i.operation5(); }}

3.5 Demeter’s Rule

Demeter’s Law is also called the least knowledge principle.

Talk only to your immediate friends and not to strangers.

The implication is that if two software entities do not need to communicate directly, then direct calls to each other should not occur and can be forwarded through a third party. Its purpose is to reduce the coupling degree between classes and improve the relative independence of modules.

The “friend” in Demeter’s rule refers to the current object itself, the member object of the current object, the object created by the current object, the method parameters of the current object, etc. These objects have association, aggregation or combination relationship with the current object, and can directly access the methods of these objects.

Now let’s look at an example to understand Demeter’s rule

Examples of the relationship between stars and agents

Celebrities are so devoted to their art that their agents handle many of the day-to-day tasks, such as meeting fans and negotiating business deals with media companies. The agents here are friends of the stars, while fans and media companies are strangers, so Demeter’s law applies.

The class diagram is as follows:

The code is as follows:

(STAR)

public class Star { private String name; public Star(String name) { this.name=name; } public String getName() { return name; }}

Fans

public class Fans { private String name; public Fans(String name) { this.name=name; } public String getName() { return name; }}

Media Company

public class Company { private String name; public Company(String name) { this.name=name; } public String getName() { return name; }}

Agent (Human)

public class Agent { private Star star; private Fans fans; private Company company; public void setStar(Star star) { this.star = star; } public void setFans(Fans fans) { this.fans = fans; } public void setCompany(Company company) { this.company = company; } public void meeting() {System.out.println(fans.getName() + fans.getName() + fans.getName() + fans.getName() + fans.getName() + fans.getName() + fans.getName() + fans.getName() + fans.getName() +" ); } public void business() {System.out.println(company.getName() + company.getName()) +" ); }}

3.6 Principles of composite reuse

The principle of composite reuse refers to: try to use association relationship such as composition or aggregation to realize, and then consider using inheritance relationship to realize.

Generally, class multiplexing can be divided into inheritance multiplexing and composite multiplexing.

Inherited multiplexing has the advantages of simplicity and ease of implementation, but it also has the following disadvantages:

  1. Inherited reuse breaks class encapsulation. Because inheritance exposes the implementation details of the parent class to the child class, the parent class is transparent to the child class, so this reuse is also known as “white box” reuse.
  2. The coupling degree between subclass and superclass is high. Any change in the implementation of the parent class will lead to changes in the implementation of the child class, which is not conducive to the expansion and maintenance of the class.
  3. It limits the flexibility of reuse. Implementations that inherit from a parent class are static and defined at compile time, so they cannot be changed at run time.

When using composite or aggregate reuse, an existing object can be incorporated into a new object, making it part of the new object, and the new object can call the functions of the existing object, which has the following advantages:

  1. It maintains class encapsulation. Because the internal details of the component object are invisible to the new object, this reuse is also known as “black box” reuse.
  2. Low coupling between objects. An abstraction can be declared at the member location of a class.
  3. High flexibility of reuse. This reuse can be done dynamically at run time, where new objects can dynamically reference objects of the same type as the component objects.

Let’s look at an example to understand the principle of composite reuse

Automobile classification management procedure

According to the “power source”, automobiles can be divided into gasoline automobiles, electric automobiles, etc. According to the “color” division can be divided into white cars, black cars and red cars. If you consider both categories, there are many combinations. The class diagram is as follows:

As we can see from the above class diagram, the use of inheritance multiplexing creates many subclasses, and if there are new power sources or new colors, new classes need to be defined. Let’s try to switch from inheritance multiplexing to aggregate multiplexing and let’s see.



Write in the last

  • If my article is useful to you, please click 👍, thank you!
  • Let me know in the comments section if you have any questions! 💪