The seven principles of Software Design you must Know

1 Design Mode

Software design should follow the design pattern, which is the concrete realization of the seven principles

Improve code flexibility, reuse, readability, extensibility and reliability. The program presents high cohesion and low coupling.

Once you understand design patterns, you understand the essence of object-oriented analysis and design (OOA/D).

2. Seven principles of design pattern

Seven principles of design patterns:

1 Single responsibility principle 2 Interface isolation principle 3 Dependency inversion principle 4 Richter substitution principle 5 Open and close principle OCP 6 Demeter rule 7 Composite reuse principle

Principle of single responsibility

3.1 Basic Introduction

For classes, and A class should be responsible for only one principle, if A is responsible for two different responsibilities: responsibility 1, responsibility 2. When the requirements of responsibility 1 change and A changes, it may cause the execution error of responsibility 2, so the granularity of class A needs to be decomposed into A1 and A2

3.2 Application Examples

  1. Option one, not consistent with a single principle of responsibility

    public class SingResponsibility1 {
    
        public static void main(String[] args) {
            Vehicle vehicle = new Vehicle();
            vehicle.run("Motorcycle");
            vehicle.run("Car");
            vehicle.run("Plane"); }}// Transportation class
    // Analysis of mode 1
    // 1. In the run method of mode 1, the single responsibility principle is violated
    // 2. The solution is very simple, according to the operation method of different vehicles, can be decomposed into different types
    class Vehicle {
    
        public void run(String vehicle){
            System.out.println(vehicle + "On the highway!"); }}Copy the code
  2. Option two is classically consistent with the single responsibility principle

    public class SingResponsibility2 {
    
        public static void main(String[] args) {
            RoadVehicle roadVehicle = new RoadVehicle();
            roadVehicle.run("Car");
            roadVehicle.run("Motorcycle");
            AirVehicle airVehicle = new AirVehicle();
            airVehicle.run("Plane");
            WaterVehicle waterVehicle = new WaterVehicle();
            waterVehicle.run("Ship"); }}// Analysis of option 2
    // 1. Observe the principle of single responsibility
    // 2. However, this is a big change, which is to decompose the class and modify the client
    // 3. Improvements: Modify the Vehicle class directly, with fewer code changes
    class RoadVehicle {
        public void run(String vehicle){
            System.out.println(vehicle + "Running on the highway!"); }}class AirVehicle {
        public void run(String vehicle){
            System.out.println(vehicle + "Fly in the sky!); }}class WaterVehicle {
        public void run(String vehicle){
            System.out.println(vehicle + "Sailing on the sea!); }}Copy the code
  3. Option three is methodologically consistent with the principle of single responsibility

    public class SingResponsibility3 {
    
        public static void main(String[] args) {
            Vehicle2 vehicle2 = new Vehicle2();
            vehicle2.run("Car");
            vehicle2.runAir("Plane");
            vehicle2.runWater("Ship"); }}// Analysis of mode 3
    // 1. This modification method does not make major changes to the original class, but only adds methods
    // 2. While the single responsibility principle is not observed at the class level, it is observed at the method level
    class Vehicle2 {
    
        public void run(String vehicle){
            System.out.println(vehicle + "On the highway!");
        }
    
        public void runAir(String vehicle){
            System.out.println(vehicle + "Running in the sky!");
        }
    
        public void runWater(String vehicle){
            System.out.println(vehicle + "Running on the sea!"); }}Copy the code

3.3 Single Responsibility Principle Notes and details

  1. Reduce the complexity of classes so that each class has only one responsibility.
  2. Improve readability and maintainability of classes.
  3. Reduce the risk of change.
  4. In general, we should adhere to the single responsibility principle, which can only be violated at the code level if the logic is simple enough; Only the number of methods in a class is small enough to maintain the single responsibility principle at the method level.

4 Interface Segregation Principle

4.1 Basic Introduction

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

  2. Let’s start with a picture

  1. Class A depends on B through interface Interface1, and class C depends on D through interface Interface1. If interface Interface1 is not the minimum interface for classes A and C, then classes B and D must implement methods and classes they don’t need.

  2. The isolation principle should be treated as follows:

    Split interface Interface1 into separate interfaces, with classes A and C dependent on the interfaces they need. That is, the interface isolation principle is adopted.

4.2 Application Examples

  1. Class A depends on class B through Interface1, and class C depends on class D through Interface1
  2. Split interfaces to achieve interface isolation
public class Segregation {

    public static void main(String[] args) {
        new A().depend1(new B());
        new C().depend4(newD()); }}interface Interface1 {
    void operation1(a);
    void operation2(a);
    void operation3(a);
    void operation4(a);
    void operation5(a);
}
class A {
    public void depend1(Interface1 i) {
        i.operation1();
    };
    public void depend2(Interface1 i) {
        i.operation2();
    };
    public void depend3(Interface1 i) {
        i.operation3();
    };
}
class B implements Interface1 {
    public void operation1(a) {
        System.out.println("B implements Operation1");
    };
    public void operation2(a) {
        System.out.println("B implements operation2");
    };
    public void operation3(a) {
        System.out.println("B implements OPERATION3");
    };
    public void operation4(a) {
        System.out.println("B implements Operation4");
    };
    public void operation5(a) {
        System.out.println("B implements Operation5");
    };
}
class C {
    public void depend1(Interface1 i) {
        i.operation1();
    };
    public void depend4(Interface1 i) {
        i.operation4();
    };
    public void depend5(Interface1 i) {
        i.operation5();
    };
}
class D implements Interface1 {
    public void operation1(a) {
        System.out.println("D implements OPERATION1");
    };
    public void operation2(a) {
        System.out.println("D implements OPERATION2");
    };
    public void operation3(a) {
        System.out.println("D implements OPERATION3");
    };
    public void operation4(a) {
        System.out.println("D implements OPERATION4");
    };
    public void operation5(a) {
        System.out.println("D implements OPERATION5");
    };
}
Copy the code

Example optimization:

public class Segregation {

    public static void main(String[] args) {
        new A().depend1(new B());
        new A().depend2(new B());
        new A().depend3(new B());
        new C().depend1(new D());
        new C().depend4(new D());
        new C().depend5(newD()); }}interface Interface1 {
    void operation1(a);
}
interface Interface2 {
    void operation2(a);
    void operation3(a);
}
interface Interface3 {
    void operation4(a);
    void operation5(a);
}

class A {
    public void depend1(Interface1 i) {
        i.operation1();
    };
    public void depend2(Interface2 i) {
        i.operation2();
    };
    public void depend3(Interface2 i) {
        i.operation3();
    };
}

class B implements Interface1.Interface2 {
    public void operation1(a) {
        System.out.println("B implements Operation1");
    };
    public void operation2(a) {
        System.out.println("B implements operation2");
    };
    public void operation3(a) {
        System.out.println("B implements OPERATION3");
    };
}

class C {
    public void depend1(Interface1 i) {
        i.operation1();
    };
    public void depend4(Interface3 i) {
        i.operation4();
    };
    public void depend5(Interface3 i) {
        i.operation5();
    };
}

class D implements Interface1.Interface3 {
    public void operation1(a) {
        System.out.println("D implements OPERATION1");
    };
    public void operation4(a) {
        System.out.println("D implements OPERATION4");
    };
    public void operation5(a) {
        System.out.println("D implements OPERATION5");
    };
}

Copy the code

5 Dependence Inversion Principle

5.1 Basic Introduction

  1. A high-level module should not depend on a low-level module; both should depend on its abstraction
  2. Abstractions should not depend on details, details should depend on abstractions
  3. The central idea of dependency inversion should be interface oriented programming
  4. The dependency inversion principle is based on the design idea that abstract things are more stable than details. An architecture based on abstraction is much more stable than one based on detail. In Java, abstraction refers to an abstract class or interface, and details are concrete implementation classes
  5. The purpose of using interfaces or abstract classes is to create specifications that do not involve any concrete operations, leaving the task of presenting the details to their implementation classes

5.2 Application Examples

Three delivery modes:

  1. As interface
  2. Constructor pass
  3. Setter method passing
public class DependencyInversion {

    public static void main(String[] args) {
        People people = new People();
        people.receive(newEmail()); }}class Email {
    public String getInfo(a) {
        return "Email message: Hello World!"; }}// 1
// 2. Wechat needs to add classes to receive other messages, and People needs to add corresponding methods
// 3. Solution: Introduce an interface IReceive, which means that the receiver realizes the IReceive interface by using wechat messages, so as to compound the dependency inversion principle
class People {
    public void receive (Email email) { System.out.println(email.getInfo()); }}Copy the code

Example optimization:

public class DependencyInversion {

    public static void main(String[] args) {
        People people = new People();
        people.receive(new Email());
        people.receive(newWeChat()); }}interface IReceive {
    String getInfo(a);
}

class Email implements IReceive {
    @Override
    public String getInfo(a) {
        return "Email message: Hello World!"; }}class WeChat implements IReceive {
    @Override
    public String getInfo(a) {
        return "Hello world!"; }}// 1
// 2. Wechat needs to add classes to receive other messages, and People needs to add corresponding methods
// 3. Solution: Introduce an interface IReceive, which means that the receiver realizes the IReceive interface by using wechat messages, so as to compound the dependency inversion principle
class People {
    public void receive (IReceive receive) { System.out.println(receive.getInfo()); }}Copy the code
public class DependencyPass {
    /** * Dependencies can be passed through open method dependencies, constructors, setters, etc@param args
     */
    public static void main(String[] args) {
        ITV changHongTV = new ChangHong();
        OpenAndClose openAndClose = newOpenAndClose(changHongTV); openAndClose.open(); }}interface IOpenAndClose {
    void open(a);
}
interface ITV {
    void play(a);
}
class ChangHong implements ITV {
    @Override
    public void play(a) {
        System.out.println("Turn on the Changhong TV"); }}class OpenAndClose implements IOpenAndClose {
    private ITV tv;
    public OpenAndClose(ITV tv) {
        this.tv = tv;
    }
    @Override
    public void open(a) { tv.play(); }}Copy the code

Notes and Details:

  1. Low-level modules should have abstract classes or interfaces, or both, for better program stability
  2. Variables are declared as abstract classes or interfaces as possible, so that there is a buffer layer between our variable references and the actual objects, using program extension and optimization
  3. Inheritance follows the Richter substitution principle

6 Liskov Substitution Principle

6.1 Thinking and explanation of inheritance

  1. Inheritance contains the implication that all methods implemented in the parent class are in effect creating specifications and contracts. Although it does not force all subclasses to follow these contracts, if the subclass arbitrarily modifies the implemented methods, it will break the inheritance system
  2. Inheritance in bring convenience to the program design at the same time, also bring disadvantages, such as using inheritance to invasive procedures, procedures of portability is reduced, increase the coupling between the objects, if a class is inherited by other classes, is when this class needs to be modified, must consider all the subclasses, and the parent class changes, All subclass functions involved are subject to failure
  3. Question posed: how to use inheritance correctly in programming? == Richter’s substitution principle

6.2 Basic Introduction

  1. It was proposed in 1988 by a woman named Li at MIT
  2. If every object O1 of type T1 has an object O2 of type T2, such that all programs P defined by T1 behave the same when all objects O1 replace O2, then type T2 is a subtype of type T1. In other words, all references to the base class must be able to transparently use objects of its subclasses
  3. When using inheritance, follow the Richter’s substitution rule and try not to override superclass methods in subclasses
  4. Inheritance actually increases the coupling of the two classes, and can be solved by aggregation, composition, and dependency, where appropriate

6.3 Application Examples

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("1-8= " + a.fun1(1.8));
        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("1-8 + 8 =" + b.fun2(1.8)); }}class A {
    public int fun1(int num1,int num2) {
        returnnum1 - num2; }}// Class B accidentally overwrites class A's methods
class B extends A {
    public int fun1(int num1,int num2) {
        return num1 + num2;
    }
    public int fun2(int num1,int num2) {
        returnfun1(num1,num2) + num2; }}Copy the code

Example optimization:

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("1-8= " + a.fun1(1.8));
        B b = new B();
        // Class B no longer inherits class A
        System.out.println("1-8 + 8 =" + b.fun2(1.8)); }}class Base {}class A extends Base {
    public int fun1(int num1,int num2) {
        returnnum1 - num2; }}// Class B accidentally overwrites class A's methods
class B extends Base {
    private A a = new A(); / /
    public int fun1(int num1,int num2) {
        return num1 + num2;
    }
    public int fun2(int num1,int num2) {
        returna.fun1(num1,num2) + num2; }}Copy the code

6.4 Solution

  1. We found an error in the subtraction function that worked well. In practical programming, we often rewrite the method of the parent class to complete the new function, which is simple to write, but the reuse of the whole inheritance system is poor, especially when running polymorphism is relatively trivial
  2. Common practice: the original parent class and child class inherit a more popular base class, the original inheritance relationship removed, replaced by dependency, aggregation, composition and other relations

7 Open Closed Principle

7.1 Basic Introduction

  1. Open and close principle is the most basic and important principle of programming
  2. A software entity such as classes, modules, and functions should be open to extension (providers) and closed to modification (users). Use abstractions to build frameworks and implementations to show details
  3. When software requirements change, try to achieve the change by extending the behavior of software entities rather than by modifying existing code
  4. The purpose of following other principles in programming and using design patterns is to follow the open closed principle

7.2 Application Examples

public class Ocp {

    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawRectangle(new Rectangle());
        graphicEditor.drawCircle(new Circle());
        // Add a triangle}}// Class for drawing
class GraphicEditor {
    public void drawShape(Shape s) {
        if (s.m_type == 1) {
            drawRectangle(s);
        } else if (s.m_type == 2) { drawCircle(s); }}public void drawRectangle(Shape s) {
        System.out.println("Draw a rectangle");
    }
    public void drawCircle(Shape s) {
        System.out.println("Draw a circle"); }}class Shape {
    int m_type;
}
class Rectangle extends Shape {
    Rectangle() {
        super.m_type = 1; }}class Circle extends Shape {
    Circle() {
        super.m_type = 2; }}Copy the code

Example optimization:

public class Ocp {

    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        // Add a triangle
        graphicEditor.drawShape(newTriangle()); }}// Class for drawing
class GraphicEditor {
    public void drawShape(Shape s) { s.draw(); }}abstract class Shape {
    int m_type;

    public abstract void draw(a);
}

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

    @Override
    public void draw(a) {
        System.out.println("Draw a rectangle"); }}class Circle extends Shape {
    Circle() {
        super.m_type = 2;
    }

    @Override
    public void draw(a) {
        System.out.println("Draw a circle"); }}class Triangle extends Shape {
    Triangle() {
        super.m_type = 3;
    }

    @Override
    public void draw(a) {
        System.out.println("Draw a triangle"); }}Copy the code

8. Demeter Principle

8.1 Basic Introduction

  1. One object should have minimal knowledge of other objects
  2. The closer the relationship between classes, the greater the coupling degree
  3. Demeter’s law is also known as the least known principle, that is, a class knows as little as possible about the class it depends on. That is to say, no matter how complex the dependent class is, it should try to encapsulate the logic inside the class and provide no information to the outside world except public methods
  4. There’s a simpler definition of Demeter’s rule: Only correspond with friends
  5. Direct friends: Every object is coupled to other objects, and as long as there is a coupling between two objects, we say they are friends. There are many ways of coupling: dependency, association, composition, aggregation, and so on. Classes that appear in member variables, method parameters, and method return values are direct friends, while classes that appear in local variables are not direct friends. That is, unfamiliar classes are best kept from appearing inside the class as local variables.

8.2 Application Example

public class Demeter {
    public static void main(String[] args) {
        SchoolManage schoolManage = new SchoolManage();
        schoolManage.printAllEmployee(newCollegeManage()); }}class Employee {
    private String id;
    public void setId(String id) {
        this.id = id;
    }
    public String getId(a) {
        returnid; }}class CollegeEmployee {
    private String id;
    public void setId(String id) {
        this.id = id;
    }
    public String getId(a) {
        returnid; }}class CollegeManage {
    public List<CollegeEmployee> getAllEmployee(a) {
        List<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("College Employee ID=" + i);
            list.add(emp);
        }
        returnlist; }}// Direct friend class Employee CollegeMange Unfamiliar: CollegeEmployee
class SchoolManage {
    public List<Employee> getAllEmployee(a) {
        List<Employee> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Employee emp = new Employee();
            emp.setId("Headquarters employee ID=" + i);
            list.add(emp);
        }
        return list;
    }
    public void printAllEmployee(CollegeManage cm) {
        List<CollegeEmployee> collegeEmployees = cm.getAllEmployee();
        System.out.println("---------------- Branch staff ------------------");
        for (CollegeEmployee emp:
             collegeEmployees) {
            System.out.println(emp.getId());
        }
        List<Employee> employees = getAllEmployee();
        System.out.println("---------------- Head Office staff ------------------");
        for(Employee emp: employees) { System.out.println(emp.getId()); }}}Copy the code

Example optimization:

public class Demeter {
    public static void main(String[] args) {
        SchoolManage schoolManage = new SchoolManage();
        schoolManage.printAllEmployee(newCollegeManage()); }}class Employee {
    private String id;
    public void setId(String id) {
        this.id = id;
    }
    public String getId(a) {
        returnid; }}class CollegeEmployee {
    private String id;
    public void setId(String id) {
        this.id = id;
    }
    public String getId(a) {
        returnid; }}class CollegeManage {
    public List<CollegeEmployee> getAllEmployee(a) {
        List<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("College Employee ID=" + i);
            list.add(emp);
        }
        return list;
    }
    public void printAllEmployee(a) {
        List<CollegeEmployee> collegeEmployees = getAllEmployee();
        System.out.println("---------------- Branch staff ------------------");
        for(CollegeEmployee emp: collegeEmployees) { System.out.println(emp.getId()); }}}class SchoolManage {
    public List<Employee> getAllEmployee(a) {
        List<Employee> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Employee emp = new Employee();
            emp.setId("Headquarters employee ID=" + i);
            list.add(emp);
        }
        return list;
    }
    public void printAllEmployee(CollegeManage cm) {
        cm.printAllEmployee();
        List<Employee> employees = getAllEmployee();
        System.out.println("---------------- Head Office staff ------------------");
        for(Employee emp: employees) { System.out.println(emp.getId()); }}}Copy the code

Notes and Details:

  1. Demeter’s law is about reducing coupling
  2. Each class reduces unnecessary dependencies, so Demeter’s law requires only a reduced coupling, not a complete absence of coupling

9 Composite Reuse Principle

9.1 Basic Introduction

Try to use composition or aggregation rather than inheritance