Writing in the front

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

Learning to address

www.bilibili.com/video/BV1G4…

www.bilibili.com/video/BV1Np…

Refer to the article

C.biancheng.net/view/1317.h…

Program source codeGitee.com/zhuang-kang…

3. Software design principles

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

3.1 Open and close principle

Open for extensions, closed for modifications. When the program needs to be extended, you cannot modify the original code to achieve a hot plug effect. In short, to make the program extensible, easy to maintain and upgrade.

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

Because abstraction has good flexibility and wide adaptability, as long as abstraction is reasonable, it can basically maintain the stability of software architecture. The changeable details in software can be extended from the abstract implementation class. When the software needs to change, it only needs to derive an implementation class to extend according to the requirements.

3.2 Richter’s substitution principle

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

Richter’s substitution rule: Wherever a base class can appear, a subclass must appear. A subclass extends the functionality of its parent class, but does not change the functionality of its parent class. In other words, when a subclass inherits from a parent class, it should try not to override the methods of the parent class, except to add new methods to accomplish new functionality.

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

Wrong demonstration of the Richter substitution principle

package com.zhuang.principle.liskov;

/ * * *@Classname Liskov
 * @DescriptionWrong demonstration of the Principle of Richter substitution *@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{
    // Return the difference between two numbers
    public int fun1(int num1,int num2){
        returnnum1-num2; }}// Class B adds A new function from class A to add two numbers and sum them 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; }}Copy the code

A correct demonstration of the Principle of Richter substitution

package com.zhuang.principle.liskov;

/ * * *@Classname Liskov2
 * @DescriptionRichter'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
    public void add(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
    }

    // General subtraction
    public void sub(int a, int b) {
        System.out.println(a + "-" + b + "="+ (a - b)); }}class Sub extends Base {
    // Subclasses are specific to multiplication
    public void mul(int a, int b) {
        System.out.println(a + "*" + b + "=" + (a * b));
    }

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

3.3 Dependency inversion principle

A high-level module should not depend on a low-level module; both should depend on its abstraction; Abstractions should not depend on details, details should depend on abstractions. Simply put, requiring that the abstraction be programmed, not the implementation, reduces the coupling between the client and the implementation module.

Wrong demonstration of the dependency inversion principle

package com.zhuang.principle.inversion;



/ * * *@Classname DependenceInversion1
 * @DescriptionError demonstration of the dependency inversion principle *@Date2021/3/15 then *@Created by dell
 */

public class DependenceInversion1 {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
        person.receive(newWeiXin()); }}// Define the interface
interface IReceiver{
    public String getInfo(a);
}

class WeiXin implements IReceiver{
    @Override
    public String getInfo(a) {
        return "Send a wechat message..."; }}class Email implements IReceiver{
    @Override
    public String getInfo(a) {
        return "Send an email message..."; }}// Interface dependencies
class Person{
    public void receive(IReceiver receiver){ System.out.println(receiver.getInfo()); }}Copy the code

Correct demonstration of the dependency inversion principle

package com.zhuang.principle.inversion;

/ * * *@Classname DependenceInversion2
 * @DescriptionCorrect demonstration of the dependency inversion principle *@Date2021/3/15 but *@Created by dell
 */

public class DependenceInversion2 {
    public static void main(String[] args) {
        Client client = new Client();
        client.receive(new Emailiml());
        client.receive(newWXimpl()); }}interface IReceive{
    public void printInfo(Integer uid);
}

class WXimpl implements IReceive {
    @Override
    public void printInfo(Integer uid) {
        System.out.println("Send wechat messages"+uid); }}class Emailiml implements IReceive {
    @Override
    public void printInfo(Integer uid) {
        System.out.println("Send email message"+uid); }}class Client{
    public void receive(IReceive receive){
        receive.printInfo(12345); }}Copy the code

Object-oriented development is a good solution to this problem, in general, the probability of the change of abstraction is very small, so that the user program depends on abstraction, implementation details also depend on abstraction. Even if the implementation details are constantly changing, the client program does not need to change as long as the abstraction remains the same. This greatly reduces the coupling between the client and the implementation details.

3.4 Interface Isolation Principle

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

Interface Isolation Principle

package com.zhuang.principle.segregation;

/ * * *@Classname Sergregation
 * @DescriptionInterface isolation rule *@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 interfaces
        d.depend5(newB()); }}interface interface1{
    void operation1(a);
}

interface interface2{
    void operation2(a);
    void operation3(a);
}

interface interface3{
    void operation4(a);
    void operation5(a);
}

class A implements interface1.interface2{

    @Override
    public void operation1(a) {
        System.out.println("A implements operation1.....");
    }

    @Override
    public void operation2(a) {
        System.out.println("A implements operation2......");
    }

    @Override
    public void operation3(a) {
        System.out.println("A implements operation3......"); }}class B implements interface1.interface3{
    @Override
    public void operation1(a) {
        System.out.println("B implements operation1.....");
    }

    @Override
    public void operation4(a) {
        System.out.println("B implements operation4.....");
    }

    @Override
    public void operation5(a) {
        System.out.println("B implements operation5....."); }}// Class C uses the interface interface1. Interface2 relies on class A using only the 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 uses the interface interface1, while interface3 relies on class B using the 1,4,5 methods
class D{
    public void depend1(interface1 i){
        i.operation1();
    }

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

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

3.5 Demeter’s Rule

Demeter’s rule is also known as the least knowledge principle.

Talk only to your immediate friends and not to strangers.

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

The “friend” in Demeter’s law refers to the current object itself, its member object, the object created by the current object, the method parameters of the current object, etc. These objects are associated, aggregated or combined with the current object, and can directly access the methods of these objects.

Let’s look at an example to understand Demeter’s rule

An example of the relationship between a star and his agent

As stars devote themselves to their art, their agents handle many daily tasks, from meeting fans to negotiating with media companies. Agents here are friends of the stars, and fans and media companies are strangers, so Demeter’s rule applies.

The class diagram is as follows:

The code is as follows:

Star Category

public class Star {
    private String name;

    public Star(String name) {
        this.name=name;
    }

    public String getName(a) {
        returnname; }}Copy the code

Fans

public class Fans {
    private String name;

    public Fans(String name) {
        this.name=name;
    }

    public String getName(a) {
        returnname; }}Copy the code

Media Companies

public class Company {
    private String name;

    public Company(String name) {
        this.name=name;
    }

    public String getName(a) {
        returnname; }}Copy the code

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(a) {
        System.out.println(fans.getName() + "With the Stars" + star.getName() + "见青了。");
    }

    public void business(a) {
        System.out.println(company.getName() + "With the Stars" + star.getName() + "Negotiate business."); }}Copy the code

3.6 Principles of composite reuse

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

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

Although inheritance reuse has the advantage of being simple and easy to implement, it also has the following disadvantages:

  1. Inheritance 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. A subclass has a high degree of coupling with its parent. Any change in the implementation of the parent class results in a change in the implementation of the subclass, which is not conducive to the extension and maintenance of the class.
  3. It limits the flexibility of reuse. An implementation inherited from a parent class is static and defined at compile time, so it cannot be changed at run time.

In combination or aggregate reuse, existing objects can be incorporated into the new object and become a part of the new object. The new object can invoke the functions of the existing object, which has the following advantages:

  1. It maintains class encapsulation. Because the internal details of component objects are invisible to the new object, this reuse is also known as “black box” reuse.
  2. Low coupling between objects. You can declare abstractions at the member locations of a class.
  3. High flexibility of reuse. This reuse can occur dynamically at run time, with new objects dynamically referencing objects of the same type as component objects.

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

An automobile classification management program

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

As you can see from the class diagram above, there are many subclasses using inheritance reuse. If there are new power sources or new colors, new classes need to be defined. Let’s try to change inheritance reuse to aggregate reuse and let’s see.

Write in the last

  • If my article is useful to you, please give me a click πŸ‘, thank you 😊!
  • If you have any questions, please point them out in the comments section! πŸ’ͺ