Dimitri's law of software design principles

Keywords: Java Design Pattern

catalogue

1, Definition of Demeter's law

2, The meaning of Dimitri's law

3, Summary

1, Definition of Demeter's law

Dimitri's law, also known as the principle of least knowledge, although its name is different, it describes the same rule: an object should have the least understanding of other objects. Generally speaking, a class should know the least about the classes it needs to couple or call. It doesn't matter to me how complex the interior of the coupled or called class is. I know that I call so many public methods you provide, and I don't care about the rest.

2, The meaning of Dimitri's law

Demeter's law clearly stipulates the low coupling of classes, which includes the following meanings.

(1) Only communicate with friends

Dimitri's law requires communication only with direct friends. What is a direct friend? Each object must have a coupling relationship with other objects. The coupling between two objects becomes a friend relationship. There are many types of relationships, such as composition, aggregation, dependency and so on. Here are some examples of how to communicate only with direct friends.

For example, the teacher asked the sports committee to confirm whether all the girls in the class were here? The class diagram is shown in the following figure:

  The implementation process is as follows:

Teachers:

public class Teacher {
    //The teacher gave an order to count the number of girls
    public void command(GroupLeader groupLeader) {
        List<Girl> girls = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            girls.add(new Girl());
        }
        //Tell the sports committee to start the inventory task
        groupLeader.countGirls(girls);
    }
}

The teacher has only one method, command, first define all the girls, and then issue an order to the sports committee to count the number of girls.

The code of sports Commissioner GroupLeader is as follows:

public class GroupLeader {
    public void countGirls(List<Girl> girlList) {
        System.out.println("Number of girls: " + girlList.size());
    }
}

Both the teacher class and the sports committee class depend on the Girl class, and the Girl class does not need to perform any action, so it is defined as follows:

public class Girl {
}

Define another scene class:

public class Client {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        teacher.command(new GroupLeader());
    }
}

The operation results are as follows:

Number of girls: 10

The sports committee counted the girls according to the teacher's requirements and got the number. Let's go back and think about the problems with this program. First, make sure that the teacher class has several friend classes. It has only one friend class - GroupLeader. Why isn't Girl a friend class? Teacher has a dependency on Girl class. The definition of friend class is as follows: the classes that appear in member variables, method input parameters and output parameters become member friend classes. The Gril class appears inside the command method, so it does not belong to teacher's direct friend.

Demeter's law tells us that a class only communicates with friend classes, but the command method we just defined communicates with Girl class and declares list < Girl > dynamic collection, which destroys the robustness of Teacher.

The problem has been found. We modify the program and slightly adjust the class diagram, as shown in the following figure:

  Modified teacher class:

public class Teacher {
    //The teacher gave an order to count the number of girls
    public void command(GroupLeader groupLeader) {
        //Tell the sports committee to start the inventory task
        groupLeader.countGirls();
    }
}

Modified GroupLeader Sports Committee class:

public class GroupLeader {
    private List<Girl> girls;

    public GroupLeader(List<Girl> girls) {
        this.girls = girls;
    }

    public void countGirls() {
        System.out.println("Number of girls: " + girls.size());
    }
}

A constructor is defined in the GroupLeader class, and the dependency is passed through the constructor. At the same time, some adjustments have been made to the scene class:

public class Client {
    public static void main(String[] args) {
        List<Girl> girls = new ArrayList<>();
        //Initialize girl information
        for (int i = 0; i < 10; i++) {
            girls.add(new Girl());
        }
        ;
        Teacher teacher = new Teacher();
        //The teacher issued an order
        teacher.command(new GroupLeader(girls));
    }
}

The program is simply modified, the initialization of Teacher's list < Girl > is moved to the scene class, and the injection of Girl is added in GroupLeader, which avoids Teacher's access to unfamiliar class Girl, reduces the coupling between systems and improves the robustness of the system.

(2) There is also a distance between friends

There is a distance between people, too far away, the relationship gradually alienated, and finally become a stranger; Too close to stab each other. Demeter's law is to describe this distance. Even friends can't say everything and know everything.

When installing software, we often have a guiding action. The first step is to confirm whether to install, the second step is to confirm Lisence, and then select the installation directory... This is a typical sequential action. Specifically, in the program, we call one or more classes, execute the first method, and then the second method, According to the returned results, we can see whether the third method or the fourth method can be called, and so on. The class diagram is as follows:

The implementation process is as follows:

public class Wizard {

    private Random random = new Random(System.currentTimeMillis());

    /**
     * First step
     *
     * @return
     */
    public int first() {
        System.out.println("Execute the first method");
        return random.nextInt(100);
    }

    /**
     * Step 2
     *
     * @return
     */
    public int second() {
        System.out.println("Execute the second method");
        return random.nextInt(100);
    }

    /**
     * Step 3
     *
     * @return
     */
    public int third() {
        System.out.println("Execute the third method");
        return random.nextInt(100);
    }

}

  Three step methods are defined in the Wizard class. In each step, relevant business logic completes the specified task. We use a random function to replace the return value of business execution.

The code of InstallSoftware class is as follows:

public class InstallSoftware {
    public void install(Wizard wizard) {
        int first = wizard.first();
        //According to the return result of first, see whether you need to execute second
        if (first > 50) {
            int second = wizard.second();
            if (second > 50) {
                int third = wizard.third();
                if (third > 50) {
                    wizard.first();
                }
            }
        }
    }
}

Decide whether to continue to execute the next method according to the execution results of each method, and simulate the manual selection operation. The scene classes are as follows:

public class Client {
    public static void main(String[] args) {
        InstallSoftware installSoftware = new InstallSoftware();
        installSoftware.install(new Wizard());
    }
}

The above program is very simple. The running results are related to random numbers. The results of each execution are different. Readers need to run and view the results themselves. Although the program is simple, the hidden problem is not simple. What's the problem with the program?

The Wizard class exposes too many methods to the InstallSoftware class. Their friendship is too close, and the coupling relationship becomes very strong. If you want to change the type of the return value of the first method in the Wizard class from int to boolean, you need to modify the InstallSoftware class to spread the risk of modification and change. Therefore, this coupling is inappropriate. We need to reconstruct the design. The reconstructed class diagram is as follows:

Add an installWizard method in the Wizard class to encapsulate the installation process, and modify all three public methods to private methods, as follows:

public class Wizard {

    private Random random = new Random(System.currentTimeMillis());

    /**
     * First step
     *
     * @return
     */
    private int first() {
        System.out.println("Execute the first method");
        return random.nextInt(100);
    }

    /**
     * Step 2
     *
     * @return
     */
    private int second() {
        System.out.println("Execute the second method");
        return random.nextInt(100);
    }

    /**
     * Step 3
     *
     * @return
     */
    private int third() {
        System.out.println("Execute the third method");
        return random.nextInt(100);
    }

    public void installWizard() {
        int first = this.first();
        //According to the return result of first, see whether you need to execute second
        if (first > 50) {
            int second = this.second();
            if (second > 50) {
                int third = this.third();
                if (third > 50) {
                    this.first();
                }
            }
        }
    }

}

Tell me, the access rights of the three steps are changed to private, and the method installWizard() in InstallSoftware is moved to the Wizard class. After such refactoring, the Wizard class only publishes a public method. Even if you want to modify the return value of the first method, it only affects the Wizard itself, and other classes are not affected, which shows the high cohesion of the class.

The modified InstallSoftware code is as follows:

public class InstallSoftware {
    public void install(Wizard wizard) {
        wizard.installWizard();
    }
}

  There is no change in the scene class. Through refactoring, the coupling relationship between classes becomes weaker, the structure is clear, and the risk caused by change becomes smaller.

The more public attributes or methods a class exposes, the greater the scope involved in modification and the greater the risk diffusion caused by change. Therefore, in order to maintain the distance between friends, it is necessary to repeatedly measure in the design: whether public attributes and methods can be reduced, whether access permissions such as private, package private and protected can be modified, and whether final keywords can be added.

(3) , is their own is their own

In practical application, such a method often appears: it can be placed in this class, and there is no mistake in placing it in other classes. How to measure it? You can check the principle that if a method is placed in this class, it will neither increase the relationship between classes nor have a negative impact on this class, it will be placed in this class.

3, Summary

The core concept of Dimitri's law is inter class decoupling and weak coupling. Only after weak coupling can the reuse rate of classes be improved. The result of its requirements is to produce a large number of transfer or jump classes, which not only increases the complexity of the system, but also brings difficulty to maintenance. Readers need to weigh repeatedly when using Dimitri's law, not only to make the structure clear, but also to achieve high cohesion and low coupling.

Reference: < < Zen of design pattern > >

Posted by amir on Tue, 12 Oct 2021 11:15:53 -0700