Design pattern learning 03 (Java implementation) – six principles of software design

Time:2021-6-22

Write on the front

  • Take notes on learning design patterns
  • Improve the flexible use of design patterns

Learning address

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

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

Reference article

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

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

3. Software design principle

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

3.1 principle of opening and closing

Open to extension and close to modification. When the program needs to be expanded, we can not modify the original code to achieve a hot swap effect. In short, it is to make the program extensible, easy to maintain and upgrade.

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

Because the abstraction is flexible and adaptable, as long as the abstraction is reasonable, the stability of software architecture can be basically maintained. When the software needs to change, it only needs to derive a new implementation class to extend.

3.2 Leeb substitution principle

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

Richter’s substitution principle: wherever the base class can appear, the subclass must appear. Popular understanding: subclass can extend the function of the parent class, but can not change the original function of the parent class. In other words, when a subclass inherits from a parent class, it should not rewrite the methods of the parent class except adding new methods to complete the new functions.

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

The wrong demonstration of Leech’s substitution principle

package com.zhuang.principle.liskov;

/**
 * @Classname Liskov
 *@ description leech substitution principle error demonstration
 * @Date 2021/3/15 13:58
 * @Created by dell
 */

public class Liskov {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("11-3=" +a.fun1(11,3));
        System.out.println("11-8=" +a.fun1(11,8));

        System.out.println("===================");

        B b = new B();
        System.out.println("11-3="+b.fun1(11,3));
        System.out.println("1-8="+b.fun1(1,8));
        System.out.println("11+3+9="+b.fun2(11,3));
    }
}

class A{
    //Returns the difference between two numbers
    public int fun1(int num1,int num2){
        return num1-num2;
    }
}

//Class B inherits from Class A and adds new functions to complete the addition of two numbers, and then sums with 9
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;
    }
}

The correct demonstration of Leech’s principle of substitution

package com.zhuang.principle.liskov;

/**
 * @Classname Liskov2
 *@ description Richter's substitution principle
 * @Date 2021/3/15 14:13
 * @Created by dell
 */

public class Liskov2 {
    public static void main(String[] args) {
        Base base = new Base();
        base.add(5,6);
        base.sub(6,2);

        Sub sub = new Sub();
        sub.mul(5,6);
        sub.div(10,2);
    }
}

class Base {
    //General addition operation
    public void add(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
    }

    //Universal subtraction
    public void sub(int a, int b) {
        System.out.println(a + "-" + b + "=" + (a - b));
    }
}

class Sub extends Base {
    //Subclass specific multiplication
    public void mul(int a, int b) {
        System.out.println(a + "*" + b + "=" + (a * b));
    }

    //Subclass specific division
    public void div(int a, int b) {
        System.out.println(a + "/" + b + "=" + (a / b));
    }
}

3.3 principle of dependence reversal

High level module should not rely on low level module, both of them should rely on its abstraction; Abstractions should not rely on details, details should rely on abstractions. In short, it is required to program the abstraction, not the implementation, so as to reduce the coupling between the client and the implementation module.

Wrong demonstration of the principle of dependence reversal

package com.zhuang.principle.inversion;



/**
 * @Classname DependenceInversion1
 *Error demonstration of @ description dependence reversal principle
 * @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());
    }
}

//Define interface
interface IReceiver{
    public String getInfo();
}

class WeiXin implements IReceiver{
    @Override
    public String getInfo() {
        Return "send wechat message...";
    }
}
class Email implements IReceiver{
    @Override
    public String getInfo() {
        Return "send email message...";
    }
}
//Interface dependency
class Person{
    public void receive(IReceiver receiver){
        System.out.println(receiver.getInfo());
    }
}

Correct demonstration of the principle of relying on inversion

package com.zhuang.principle.inversion;

/**
 * @Classname DependenceInversion2
 *@ description rely on reverse principle to demonstrate correctly
 * @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 ("send wechat message" + 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 change probability of abstraction is very small, which makes the user program depend on abstraction, and the implementation details also depend on abstraction. Even if the implementation details are constantly changing, as long as the abstraction remains unchanged, the client program does not need to change. This greatly reduces the coupling between client program and implementation details.

3.4 interface isolation principle

The client should not be forced to rely on methods it does not use; The dependence of one class on another should be based on the smallest interface.

Interface isolation principle

package com.zhuang.principle.segregation;

/**
 * @Classname Sergregation
 *@ description interface isolation principle
 * @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());// Class C relies on class a through interfaces
        c.depend3(new A());

        System.out.println("=======================");

        D d = new D();
        d.depend1(new B());
        d.depend4(new B());// Class D relies on class B through 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 operation1...);
    }

    @Override
    public void operation2() {
        System. Out. Println ("a implements Operation2...);
    }

    @Override
    public void operation3() {
        System. Out. Println ("a implements operation 3...);
    }
}

class B implements interface1,interface3{
    @Override
    public void operation1() {
        System. Out. Println ("B implements operation1...);
    }

    @Override
    public void operation4() {
        System. Out. Println ("B implements operation 4...);
    }

    @Override
    public void operation5() {
        System. Out. Println ("B implements operation5....");
    }
}

//Class C relies on interface 1 and interface 2. Class a only uses 1, 2 and 3 methods
class C{
    public void depend1(interface1 i){
        i.operation1();
    }

    public void depend2(interface2 i){
        i.operation2();
    }

    public void depend3(interface2 i){
        i.operation3();
    }
}

//Class D relies on class B through interface 1 and interface 3, and uses methods 1, 4 and 5
class D{
    public void depend1(interface1 i){
        i.operation1();
    }

    public void depend4(interface3 i){
        i.operation4();
    }

    public void depend5(interface3 i){
        i.operation5();
    }
}

3.5 Demeter’s law

Dimiter’s law is also called the least knowledge principle.

Talk only to your immediate friends and not to strangers.

Its meaning is: if two software entities do not need to communicate directly, then they should not call each other directly. They can forward the call through a third party. The purpose is to reduce the coupling between classes and improve the relative independence of modules.

The “friend” in Dimiter’s Law 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.

Let’s take a look at an example to understand Dimiter’s law

An example of the relationship between stars and agents

Stars devote themselves to art, so many daily affairs are handled by agents, such as meeting with fans, business negotiation with media companies, etc. The agents here are friends of stars, while fans and media companies are strangers, so it’s appropriate to use the Dimiter rule.

The class diagram is as follows:

Design pattern learning 03 (Java implementation) - six principles of software design

The code is as follows:

Stars

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

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() + "meet the stars" + star. Getname() + "meet you.");
    }

    public void business() {
        System. Out. Println (company. Getname() + "with star" + star. Getname() + "business negotiation");
    }
}

3.6 principle of composite multiplexing

The principle of composite reuse is to use association relations such as composition or aggregation first, and then consider inheritance relations.

Generally, class reuse is divided into inheritance reuse and composition reuse.

Although inheritance reuse is simple and easy to implement, it also has the following disadvantages

  1. Inheritance reuse destroys the encapsulation of classes. Because inheritance will expose the implementation details of the parent class to the subclass, and the parent class is transparent to the subclass, this reuse is also called “white box” reuse.
  2. The coupling degree between subclass and parent is high. Any change in the implementation of the parent class will lead to changes in the implementation of the subclass, which is not conducive to the expansion and maintenance of the class.
  3. It limits the flexibility of reuse. The implementation inherited from the parent class is static and defined at compile time, so it cannot change at run time.

When using combination or aggregation reuse, existing objects can be incorporated into new objects and become a part of new objects. New objects can call the functions of existing objects. It has the following advantages:

  1. It maintains the encapsulation of the class. Because the internal details of component objects are invisible to new objects, this kind of reuse is also called “black box” reuse.
  2. The coupling between objects is low. Abstractions can be declared at the member position of a class.
  3. High flexibility of reuse. This reuse can be done dynamically at runtime, and new objects can refer to objects of the same type as component objects dynamically.

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

Automobile classification management procedure

According to “power source”, automobile can be divided into gasoline automobile, electric automobile, etc; According to “color”, it can be divided into white car, black car and red car. If we consider these two classifications at the same time, there will be a lot of combinations. The class diagram is as follows:

Design pattern learning 03 (Java implementation) - six principles of software design

From the above class diagram, we can see that many subclasses have been generated by using inheritance reuse. If there are new power sources or new colors, we need to define new classes. Let’s try to change inheritance reuse to aggregate reuse.

Design pattern learning 03 (Java implementation) - six principles of software design
Write at the end

  • If my article is useful to you, please give me some, thank you!
  • If you have any questions, please point them out in the comments area!