Seven principles of design mode

Seven principles of design mode

1. Purpose of design mode

In the process of writing software, programmers are faced with many conditions from coupling, cohesion, maintainability, scalability, reusability, flexibility and so on. The design pattern is to make the program (software) better.

  • Reusability of code (i.e. code with the same function does not need to be written many times)
  • Readability (i.e. programming standardization, which is convenient for other programmers to read and understand)
  • Scalability (that is, when new functions need to be added, it is very convenient and can be called maintainability)
  • Reliability (i.e. when we add new functions, it has no impact on the original functions)
  • The program has the characteristics of high cohesion and low coupling
  • Share the golden sentence:
    • Design patterns contain the essence of object-oriented. "If you understand design patterns, you will understand the essence of object-oriented analysis and design."
    • Soctt Mayers once said in his masterpiece Effective C + +: the difference between a C + + veteran and a C + + novice is that the former has many scars on the back of his hand

2. Seven principles of design mode

The principle of design pattern is actually the principle that programmers should abide by when programming, and it is also the basis of various design patterns (i.e. the basis for why design patterns are designed like this)

Seven principles commonly used in design mode are:

  1. Single responsibility principle
  2. Interface isolation principle
  3. Dependence Inversion Principle
  4. Richter substitution principle
  5. Opening and closing principle
  6. Dimitt's law
  7. Synthetic Reuse Principle

3. Principle of single responsibility

3.1 basic introduction

For a class, that is, a class should be responsible for only one responsibility. For example, class A is responsible for two different responsibilities: Responsibility 1 and responsibility 2 When a is changed due to the change of responsibility 1 requirements, the implementation error of responsibility 2 may be caused. Therefore, the granularity of a needs to be decomposed into A1 and A2.

3.2 application examples

Take transportation as an example:

  • Option 1

    public class SingleResponsibility1 {
        public static void main(String[] args) {
            Vehicle vehicle = new Vehicle();
            vehicle.run("motorcycle");
            vehicle.run("automobile");
            vehicle.run("aircraft");
        }
    }
    // Vehicles
    // Mode 1
    // 1. In the run method of mode 1, the principle of single responsibility is violated
    // 2. The solution is very simple. It can be decomposed into different types according to different operation methods of vehicles
    class Vehicle {
        public void run(String vehicle) {
            System.out.println(vehicle + "Running on the highway...");
        }
    }
    
  • Option 2

    public class SingleResponsibility2 {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            RoadVehicle roadVehicle = new RoadVehicle();
            roadVehicle.run("motorcycle");
            roadVehicle.run("automobile");
            AirVehicle airVehicle = new AirVehicle();
            airVehicle.run("aircraft");
        }
    }
    //Analysis of scheme 2
    //1. Abide by the principle of single responsibility
    //2. However, the change is very big, that is to decompose the class and modify the client at the same time
    //3. Improvement: directly modify the Vehicle class, and the changed code will be less = > scheme 3
    class RoadVehicle {
        public void run(String vehicle) {
            System.out.println(vehicle + "Highway operation");
        }
    }
    class AirVehicle {
        public void run(String vehicle) {
            System.out.println(vehicle + "Sky running");
        }
    }
    class WaterVehicle {
        public void run(String vehicle) {
            System.out.println(vehicle + "Water operation");
        }
    }
    
  • Option 3

    public class SingleResponsibility3 {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Vehicle2 vehicle2  = new Vehicle2();
            vehicle2.run("automobile");
            vehicle2.runWater("ship");
            vehicle2.runAir("aircraft");
        }
    }
    //Analysis of mode 3
    //1. This modification method does not make major modifications to the original class, but only adds methods
    //2. Although the principle of single responsibility is not observed at the class level, it is still observed at the method level
    class Vehicle2 {
        public void run(String vehicle) {
            //handle
            System.out.println(vehicle + " Running 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 + " Walk in the water....");
        }
        //Method 2
        //..
    }
    

3.3 precautions and details

  1. Reduce class complexity. A class is responsible for only one responsibility
  2. Improve the readability and maintainability of the class
  3. Reduce risks caused by changes
  4. Generally, we should abide by the principle of single responsibility. Only when the logic is simple enough, can we violate the principle of single responsibility at the code level: only when the number of methods in the class is small enough, can we maintain the principle of single responsibility at the method level

4. Interface isolation principle

4.1 basic introduction

  • The client should not rely on interfaces it does not need, that is, the dependence of one class on another should be based on the smallest interface

  • First look at a picture:

  • Class a depends on class B through Interface, and class C depends on class D through Interface Interface1. If Interface Interface1 is not the smallest Interface for class A and class C, class B and class D must implement the interfaces they do not need.

  • According to the principle of isolation, it should be handled as follows:

    Interface Interface1 is divided into several independent interfaces (here we divide them into three interfaces). Class A and class C establish dependencies with the interfaces they need, that is, the principle of interface isolation is adopted

4.2 application examples

  • There is no code implemented using the interface isolation principle

    //Interface
    interface Interface1 {
        void operation1();
        void operation2();
        void operation3();
        void operation4();
        void operation5();
    }
    class B implements Interface1 {
        @Override
        public void operation1() {
            System.out.println("B Realized operation1");
        }
        @Override
        public void operation2() {
            System.out.println("B Realized operation2");
        }
        @Override
        public void operation3() {
            System.out.println("B Realized operation3");
        }
        @Override
        public void operation4() {
            System.out.println("B Realized operation4");
        }
        @Override
        public void operation5() {
            System.out.println("B Realized operation5");
        }
    }
    class D implements Interface1 {
        @Override
        public void operation1() {
            System.out.println("D Realized operation1");
        }
        @Override
        public void operation2() {
            System.out.println("D Realized operation2");
        }
        @Override
        public void operation3() {
            System.out.println("D Realized operation3");
        }
        @Override
        public void operation4() {
            System.out.println("D Realized operation4");
        }
        @Override
        public void operation5() {
            System.out.println("D Realized operation5");
        }
    }
    class A { //Class A relies on (uses) class B through interface Interface1, but only uses methods 1, 2 and 3
    
        public void depend1(Interface1 i) {
            i.operation1();
        }
        public void depend2(Interface1 i) {
            i.operation2();
        }
        public void depend3(Interface1 i) {
            i.operation3();
        }
    }
    class C { //Class C relies on (uses) class D through interface Interface1, but only uses methods 1, 4 and 5
        public void depend1(Interface1 i) {
            i.operation1();
        }
        public void depend4(Interface1 i) {
            i.operation4();
        }
        public void depend5(Interface1 i) {
        }
    }
    

4.3 the interface principle should be used to improve the problems of traditional methods

  • Class a depends on class B through interface Interface1, and class C depends on class D through interface Interface1. If interface Interface1 is not the smallest interface for class A and class C, class B and class D must implement methods they do not need

  • Interface Interface1 is divided into several independent interfaces, and class A and class C establish dependencies with the interfaces they need respectively. That is, the principle of interface isolation is adopted

  • The methods in interface Interface1 are divided into three interfaces according to the actual situation

  • code implementation

    public class Segregation1 {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            // Use one
            A a = new A();
            a.depend1(new B()); // Class A depends on class B through the interface
            a.depend2(new B());
            a.depend3(new B());
            C c = new C();
            c.depend1(new D()); // Class C relies on (uses) class D through the interface
            c.depend4(new D());
            c.depend5(new D());
        }
    }
    // Interface 1
    interface Interface1 {
        void operation1();
    }
    // Interface 2
    interface Interface2 {
        void operation2();
        void operation3();
    }
    // Interface 3
    interface Interface3 {
        void operation4();
        void operation5();
    }
    class B implements Interface1, Interface2 {
        @Override
        public void operation1() {
            System.out.println("B Realized operation1");
        }
        @Override
        public void operation2() {
            System.out.println("B Realized operation2");
        }
        @Override
        public void operation3() {
            System.out.println("B Realized operation3");
        }
    }
    class D implements Interface1, Interface3 {
        @Override
        public void operation1() {
            System.out.println("D Realized operation1");
        }
        @Override
        public void operation4() {
            System.out.println("D Realized operation4");
        }
        @Override
        public void operation5() {
            System.out.println("D Realized operation5");
        }
    }
    class A { // Class A passes through interface interface1 and interface2 depend on (use) class B, but only methods 1, 2 and 3 will be used
        public void depend1(Interface1 i) {
            i.operation1();
        }
        public void depend2(Interface2 i) {
            i.operation2();
        }
        public void depend3(Interface2 i) {
            i.operation3();
        }
    }
    class C { // Class C passes through interface interface1. Interface3 depends on (uses) class D, but only uses methods 1, 4 and 5
        public void depend1(Interface1 i) {
            i.operation1();
        }
        public void depend4(Interface3 i) {
            i.operation4();
        }
        public void depend5(Interface3 i) {
            i.operation5();
        }
    }
    
  • UML diagram comparison

    Before improvement

    After improvement

5. Reliance reversal principle

5.1 basic introduction

The dependency inversion principle refers to:

  1. High level modules should not rely on low-level modules, and both should rely on their abstraction
  2. Abstract should not rely on details, details should rely on abstraction
  3. The central idea of dependency inversion is interface oriented programming
  4. The dependency inversion principle is based on the design concept that abstract things are more stable than the variability of details. Abstract based architecture is much more stable than detail based architecture. In java, abstraction refers to interfaces or abstract classes, and details are concrete implementation classes
  5. The purpose of using interfaces or abstract classes is to formulate specifications without involving any specific operations, and hand over the task of showing details to their implementation classes

5.2 application examples

Please program to complete the function of Person receiving messages.

  1. Implementation scheme 1

    public class DependecyInversion {
        public static void main(String[] args) {
            Person person = new Person();
            person.receive(new Email());
        }
    }
    class Email {
        public String getInfo() {
            return "Email message: hello,world";
        }
    }
    //Complete the function of Person receiving messages
    //Mode 1 Analysis
    //1. Simple and easy to think of
    //2. If the objects we get are wechat, SMS, etc., add new classes, and Perons should also add corresponding receiving methods
    //3. Solution: introduce an abstract interface IReceiver to represent the receiver, so that the Person class depends on the interface IReceiver
    //   Because Email, WeiXin, etc. belong to the receiving scope, it is ok for them to implement IReceiver interface respectively, so we will rely on the inversion principle of symbol dependence
    class Person {
        public void receive(Email email ) {
            System.out.println(email.getInfo());
        }
    }
    
  2. Implementation scheme 2

    public class DependecyInversion {
        public static void main(String[] args) {
            //The client does not need to be changed
            Person person = new Person();
            person.receive(new Email());
            person.receive(new WeiXin());
        }
    }
    //Define interface
    interface IReceiver {
        public String getInfo();
    }
    class Email implements IReceiver {
        @Override
        public String getInfo() {
            return "Email message: hello,world";
        }
    }
    //Add wechat
    class WeiXin implements IReceiver {
        @Override
        public String getInfo() {
            return "Wechat information: hello,ok";
        }
    }
    //Mode 2
    class Person {
        //Here we are dependent on the interface
        public void receive(IReceiver receiver ) {
            System.out.println(receiver.getInfo());
        }
    }
    

    5.3 three methods and application cases of dependency transmission

    1. Interface transfer

    2. Construction method transfer

    3. setter mode transmission

    4. Code example

      public class DependencyPass {
          public static void main(String[] args) {
              // TODO Auto-generated method stub
              ChangHong changHong = new ChangHong();
      //		OpenAndClose openAndClose = new OpenAndClose();
      //		openAndClose.open(changHong);
              //Dependency passing through constructor
      //		OpenAndClose openAndClose = new OpenAndClose(changHong);
      //		openAndClose.open();
              //Dependency passing through setter method
              OpenAndClose openAndClose = new OpenAndClose();
              openAndClose.setTv(changHong);
              openAndClose.open();
          }
      }
      // Method 1: implement dependency through interface transfer
      // Interface of switch
      // interface IOpenAndClose {
      // public void open(ITV tv); // Abstract method, receiving interface
      // }
      // Interface ITV {/ / ITV interface
      // public void play();
      // }
      // class ChangHong implements ITV {
      //	@Override
      //	public void play() {
      //		// TODO Auto-generated method stub
      //		System.out.println("Changhong TV, on");
      //	}
      // }
       Implementation interface
      // class OpenAndClose implements IOpenAndClose{
      // public void open(ITV tv){
      // tv.play();
      // }
      // }
      // Method 2: pass dependency through construction method
      //interface IOpenAndClose {
      //    public void open(); // Abstract method
      //}
      //Interface ITV {/ / ITV interface
      //    public void play();
      //}
      //class OpenAndClose implements IOpenAndClose {
      //    public ITV tv; // member
      //
      //    public OpenAndClose(ITV tv) {/ / constructor
      //        this.tv = tv;
      //    }
      //    public void open() {
      //        this.tv.play();
      //    }
      //}
      // Method 3: pass through setter method
      interface IOpenAndClose {
          void open(); // Abstract method
          void setTv(ITV tv);
      }
      interface ITV { // ITV interface
          void play();
      }
      class OpenAndClose implements IOpenAndClose {
          private ITV tv;
          public void setTv(ITV tv) {
              this.tv = tv;
          }
          public void open() {
              this.tv.play();
          }
      }
      class ChangHong implements ITV {
          @Override
          public void play() {
              // TODO Auto-generated method stub
              System.out.println("Changhong TV, turn it on");
          }
      }
      

    5.4 precautions and details

    1. Low level modules should have abstract classes or interfaces, or both, for better program stability
    2. The declaration type of variables should be abstract classes or interfaces as far as possible, so that there is a buffer layer between our variable references and actual objects, which is conducive to program expansion and optimization
    3. Follow the principle of Richter substitution when inheriting

6. Richter substitution principle

6.1 thinking and explanation of inheritance in OO

  1. Inheritance contains such a layer of meaning: all implemented methods in the parent class are actually setting specifications and contracts. Although it does not force all subclasses to follow these contracts, if subclasses arbitrarily modify these implemented methods, it will damage the whole inheritance system.
  2. Inheritance not only brings convenience to program design, but also brings disadvantages. For example, using inheritance will bring invasiveness to the program, reduce the portability of the program and increase the coupling between objects. If a class is inherited by other classes, when the class needs to be modified, all subclasses must be considered, and after the parent class is modified, all functions involving subclasses may fail
  3. The question is: how to use inheritance correctly in programming? = > Richter substitution principle

6.2 basic introduction

  1. The Liskov Substitution Principle was proposed by a woman surnamed Li of MIT in 1988.
  2. If there is object o2 of type T2 for each object o1 of type T1, so that the behavior of program P does not change when all objects o1 are replaced with o2, then type T2 is a subtype of type T1. In other words, all references to the base class must be able to use the objects of its subclasses transparently.
  3. When using inheritance, follow the Richter substitution principle and try not to override the methods of the parent class in the subclass
  4. The Richter substitution principle tells us that inheritance actually enhances the coupling between the two classes. Under appropriate circumstances, the problem can be solved through aggregation, composition and dependency.

6.3 problems and thoughts arising from a program

It's time to look at a program and think about problems and solutions

public class Liskov {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("11-3=" + a.func1(11, 3));
        System.out.println("1-8=" + a.func1(1, 8));
        System.out.println("-----------------------");
        B b = new B();
        System.out.println("11-3=" + b.func1(11, 3));//The original intention here is to find 11-3
        System.out.println("1-8=" + b.func1(1, 8));
        System.out.println("11+3+9=" + b.func2(11, 3));
    }
}
// Class A
class A {
    // Returns the difference between two numbers
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}
// Class B inherits class A
// Added a new function: complete the addition of two numbers, and then sum with 9
class B extends A {
    //Here, the method of class A is rewritten, which may be unconscious
    public int func1(int a, int b) {
        return a + b;
    }
    public int func2(int a, int b) {
        return func1(a, b) + 9;
    }
}

6.4 solutions

  1. We found an error in the subtraction function that was working normally. The reason is that class B inadvertently rewrites the method of the parent class, resulting in errors in the original function. In actual programming, we often complete new functions by rewriting the parent class. Although it is simple to write, the reusability of the whole inheritance system will be poor. Especially when running polymorphism more frequently

  2. The common approach is: the original parent and child classes inherit a more popular base class, remove the original inheritance relationship, and replace it with dependency, aggregation, combination and other relationships

  3. Improvement scheme

    Code demonstration

    public class Liskov {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            A a = new A();
            System.out.println("11-3=" + a.func1(11, 3));
            System.out.println("1-8=" + a.func1(1, 8));
            System.out.println("-----------------------");
            B b = new B();
            //Because class B no longer inherits class A, the caller will no longer func1 be subtracting
            //The function completed by the call will be clear
            System.out.println("11+3=" + b.func1(11, 3));//The original intention here is to find 11 + 3
            System.out.println("1+8=" + b.func1(1, 8));// 1+8
            System.out.println("11+3+9=" + b.func2(11, 3));
            //Class A related methods can still be used in combination
            System.out.println("11-3=" + b.func3(11, 3));// The original intention here is to find 11-3
        }
    }
    
    //Create a more basic base class
    class Base {
        //Write more basic methods and members to the Base class
    }
    // Class A
    class A extends Base {
        // Returns the difference between two numbers
        public int func1(int num1, int num2) {
            return num1 - num2;
        }
    }
    // Class B inherits class A
    // Added a new function: complete the addition of two numbers, and then sum with 9
    class B extends Base {
        //If B needs to use the method of class A, use the combination relationship
        private A a = new A();
        //Here, the method of class A is rewritten, which may be unconscious
        public int func1(int a, int b) {
            return a + b;
        }
        public int func2(int a, int b) {
            return func1(a, b) + 9;
        }
        //We still want to use the method of A
        public int func3(int a, int b) {
            return this.a.func1(a, b);
        }
    }
    

7. Opening and closing principle

7.1 basic introduction

  • Open Closed Principle is the most basic and important design principle in programming
  • A software entity such as class, module and function should be open to extension (to the provider) and closed to modification (to the user). Build the framework with abstraction and extend the details with implementation.
  • When the software needs to change, try to realize the change by expanding the behavior of the software entity, rather than by modifying the existing code.
  • The purpose of following other principles in programming and using design patterns is to follow the opening and closing principles.

7.2 look at the following code

  • Look at a function of drawing graphics. The class diagram is as follows:

  • Code demonstration

    public class Ocp {
        public static void main(String[] args) {
            //Use to see the existing problems
            GraphicEditor graphicEditor = new GraphicEditor();
            graphicEditor.drawShape(new Rectangle());
            graphicEditor.drawShape(new Circle());
        }
    }
    //This is a class for drawing [user]
    class GraphicEditor {
        //Receive the Shape object, and then draw different shapes according to the type
        public void drawShape(Shape s) {
            if (s.m_type == 1)
                drawRectangle(s);
            else if (s.m_type == 2)
                drawCircle(s);
        }
        //draw rectangle
        public void drawRectangle(Shape r) {
            System.out.println(" draw rectangle ");
        }
        //Draw circle
        public void drawCircle(Shape r) {
            System.out.println(" Draw circle ");
        }
    }
    //Shape class, base class
    class Shape {
        int m_type;
    }
    class Rectangle extends Shape {
        Rectangle() {
            super.m_type = 1;
        }
    }
    class Circle extends Shape {
        Circle() {
            super.m_type = 2;
        }
    }
    

7.3 advantages and disadvantages of mode 1

  1. The advantages are easy to understand, simple and easy to operate.

  2. The disadvantage is that it violates the ocp principle of design pattern, that is, it is open to extension (provider) and closed to modification (user). That is, when we add new functions to the class, we try not to modify the code, or modify the code as little as possible

  3. For example, if we want to add a new type of triangle, we need to make the following modifications. There are many modifications

  4. Code demonstration

    public class Ocp {
        public static void main(String[] args) {
            //Use to see the existing problems
            GraphicEditor graphicEditor = new GraphicEditor();
            graphicEditor.drawShape(new Rectangle());
            graphicEditor.drawShape(new Circle());
            graphicEditor.drawShape(new Triangle());
        }
    }
    //This is a class for drawing [user]
    class GraphicEditor {
        //Receive the Shape object, and then draw different shapes according to the type
        public void drawShape(Shape s) {
            if (s.m_type == 1)
                drawRectangle(s);
            else if (s.m_type == 2)
                drawCircle(s);
            else if (s.m_type == 3)
                drawTriangle(s);
        }
        //draw rectangle
        public void drawRectangle(Shape r) {
            System.out.println(" draw rectangle ");
        }
        //Draw circle
        public void drawCircle(Shape r) {
            System.out.println(" Draw circle ");
        }
        //Draw triangle
        public void drawTriangle(Shape r) {
            System.out.println(" Draw triangle ");
        }
    }
    //Shape class, base class
    class Shape {
        int m_type;
    }
    class Rectangle extends Shape {
        Rectangle() {
            super.m_type = 1;
        }
    }
    class Circle extends Shape {
        Circle() {
            super.m_type = 2;
        }
    }
    //Add draw triangle
    class Triangle extends Shape {
        Triangle() {
            super.m_type = 3;
        }
    }
    

7.4 analysis of improvement ideas

  • Idea: * *: * * make the Shape class into an abstract class, and provide an abstract draw method for the subclass to implement. In this way, when we have a new graphic type, we only need to let the new graphic class inherit the Shape and implement the draw method, and the user's code does not need to be modified - > meets the opening and closing principle

  • Class diagram

  • Code demonstration

    public class Ocp {
        public static void main(String[] args) {
            //Use to see the existing problems
            GraphicEditor graphicEditor = new GraphicEditor();
            graphicEditor.drawShape(new Rectangle());
            graphicEditor.drawShape(new Circle());
            graphicEditor.drawShape(new Triangle());
            graphicEditor.drawShape(new OtherGraphic());
        }
    }
    //This is a class for drawing [user]
    class GraphicEditor {
        //Receive the Shape object and call the draw method
        public void drawShape(Shape s) {
            s.draw();
        }
    }
    //Shape class, base class
    abstract class Shape {
        int m_type;
        public abstract void draw();//Abstract method
    }
    
    class Rectangle extends Shape {
        Rectangle() {
            super.m_type = 1;
        }
        @Override
        public void draw() {
            System.out.println(" draw rectangle ");
        }
    }
    class Circle extends Shape {
        Circle() {
            super.m_type = 2;
        }
        @Override
        public void draw() {
            System.out.println(" Draw circle ");
        }
    }
    //Add draw triangle
    class Triangle extends Shape {
        Triangle() {
            super.m_type = 3;
        }
        @Override
        public void draw() {
            System.out.println(" Draw triangle ");
        }
    }
    //Add a new graphic
    class OtherGraphic extends Shape {
        OtherGraphic() {
            super.m_type = 4;
        }
        @Override
        public void draw() {
            System.out.println(" Draw other graphics ");
        }
    }
    

8. Dimitri's law

8.1 basic introduction

  1. One object should have minimal knowledge of other objects
  2. The closer the relationship between classes, the greater the degree of coupling
  3. The Demeter principle is also called the least known principle, that is, the less a class knows about the class it depends on, the better. In other words, no matter how complex the dependent class is, try to encapsulate the logic inside the class. In addition to the public method provided, it will not disclose any information
  4. There is a simpler definition of Dimitri's Law: only communicate with direct friends
  5. Direct friend: each object will have a coupling relationship with other objects. As long as there is a coupling relationship between two objects, we say that the two objects are friends. There are many ways of coupling, such as dependency, association, combination, aggregation and so on. Among them, we call the classes that appear in member variables, method parameters and method return values as direct friends, while the classes that appear in local variables are not direct friends. In other words, unfamiliar classes should not appear inside the class in the form of local variables.

8.2 application examples

  1. There is a school with subordinate colleges and headquarters. Now it is required to print out the ID of the staff of the school headquarters and the ID of the staff of the college

  2. Programming to achieve the above functions, see the code demonstration

    //client
    public class Demeter1 {
        public static void main(String[] args) {
            //Created a SchoolManager object
            SchoolManager schoolManager = new SchoolManager();
            //Output the employee id of the college and the employee information of the school headquarters
            schoolManager.printAllEmployee(new CollegeManager());
        }
    }
    //School headquarters staff
    class Employee {
        private String id;
        public void setId(String id) {
            this.id = id;
        }
        public String getId() {
            return id;
        }
    }
    //Staff of the College
    class CollegeEmployee {
        private String id;
        public void setId(String id) {
            this.id = id;
        }
        public String getId() {
            return id;
        }
    }
    //Management of staff in the school of management
    class CollegeManager {
        //All employees returning to the College
        public List<CollegeEmployee> getAllEmployee() {
            List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
            for (int i = 0; i < 10; i++) { //Here we added 10 employees to the list
                CollegeEmployee emp = new CollegeEmployee();
                emp.setId("College staff id= " + i);
                list.add(emp);
            }
            return list;
        }
    }
    //School management
    //Analyze the direct friend classes of the SchoolManager class, such as Employee and CollegeManager
    //CollegeEmployee is not a direct friend, but a strange class, which violates Dimitri's law
    class SchoolManager {
        //Employees returning to the school headquarters
        public List<Employee> getAllEmployee() {
            List<Employee> list = new ArrayList<Employee>();
            for (int i = 0; i < 5; i++) { //Here we added five employees to the list
                Employee emp = new Employee();
                emp.setId("School headquarters staff id= " + i);
                list.add(emp);
            }
            return list;
        }
        //This method completes the output of the information (id) of the school headquarters and college employees
        void printAllEmployee(CollegeManager sub) {
            //Analyze problems
            //1. The CollegeEmployee here is not a direct friend of SchoolManager
            //2. CollegeEmployee appears in SchoolManager as a local variable
            //3. Violation of Dimitri's law
            //Get college employees
            List<CollegeEmployee> list1 = sub.getAllEmployee();
            System.out.println("------------College staff------------");
            for (CollegeEmployee e : list1) {
                System.out.println(e.getId());
            }
            //Get to the staff of the school headquarters
            List<Employee> list2 = this.getAllEmployee();
            System.out.println("------------School headquarters staff------------");
            for (Employee e : list2) {
                System.out.println(e.getId());
            }
        }
    }
    

8.3 application example improvement

  1. The problem with the previous design is that in SchoolManager, the CollegeEmployee class is not a direct friend (analysis) of the SchoolManager class

  2. According to Demeter's law, such coupling of indirect friends in classes should be avoided

  3. Improve the code according to Dimitri's Law:

    //client
    public class Demeter1 {
        public static void main(String[] args) {
            System.out.println("~~~Improvement of using Dimitri's law~~~");
            //Created a SchoolManager object
            SchoolManager schoolManager = new SchoolManager();
            //Output the employee id of the college and the employee information of the school headquarters
            schoolManager.printAllEmployee(new CollegeManager());
        }
    }
    //School headquarters staff
    class Employee {
        private String id;
        public void setId(String id) {
            this.id = id;
        }
        public String getId() {
            return id;
        }
    }
    //Staff of the College
    class CollegeEmployee {
        private String id;
        public void setId(String id) {
            this.id = id;
        }
        public String getId() {
            return id;
        }
    }
    //Management of staff in the school of management
    class CollegeManager {
        //All employees returning to the College
        public List<CollegeEmployee> getAllEmployee() {
            List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
            for (int i = 0; i < 10; i++) { //Here we added 10 employees to the list
                CollegeEmployee emp = new CollegeEmployee();
                emp.setId("College staff id= " + i);
                list.add(emp);
            }
            return list;
        }
        //Output information of College employees
        public void printEmployee() {
            //Get college employees
            List<CollegeEmployee> list1 = getAllEmployee();
            System.out.println("------------College staff------------");
            for (CollegeEmployee e : list1) {
                System.out.println(e.getId());
            }
        }
    }
    //School management
    //Analyze the direct friend classes of the SchoolManager class, such as Employee and CollegeManager
    //CollegeEmployee is not a direct friend, but a strange class, which violates Dimitri's law
    class SchoolManager {
        //Employees returning to the school headquarters
        public List<Employee> getAllEmployee() {
            List<Employee> list = new ArrayList<Employee>();
            for (int i = 0; i < 5; i++) { //Here we added five employees to the list
                Employee emp = new Employee();
                emp.setId("School headquarters staff id= " + i);
                list.add(emp);
            }
            return list;
        }
        //This method completes the output of the information (id) of the school headquarters and college employees
        void printAllEmployee(CollegeManager sub) {
            //Analyze problems
            //1. Encapsulate the employee method of the output college into the CollegeManager
            sub.printEmployee();
            //Get to the staff of the school headquarters
            List<Employee> list2 = this.getAllEmployee();
            System.out.println("------------School headquarters staff------------");
            for (Employee e : list2) {
                System.out.println(e.getId());
            }
        }
    }
    

8.4 precautions and details

  1. The core of dimitt's law is to reduce the coupling between classes
  2. But note: since each class reduces unnecessary dependencies, the Demeter rule only requires reducing the coupling relationship between classes (objects), not requiring no dependencies at all

9. Composite Reuse Principle

9.1 basic introduction

  • The principle is to try to use composition / aggregation instead of inheritance

10. Design principles and core ideas

  1. Find out what changes may be needed in the application, separate them, and don't mix them with the code that doesn't need to change.
  2. Programming for interfaces, not for implementations.
  3. Strive for loose coupling design between interactive objects

Tags: Java Design Pattern

Posted by darknuke on Sat, 16 Apr 2022 19:29:53 +0930