Law of Demeter original definition

87 views Asked by At

I've been trying to understand Law of Demeter but there is so much conflicting information and opinions on it, that i decided to read the original research paper (1988) (https://www2.ccs.neu.edu/research/demeter/papers/law-of-demeter/oopsla88-law-of-demeter.pdf) - but i'm very confused by one of the rules in it.

The rules are as follows:

For all classes C, and for all methods M attached to C, all objects to which M sends a message must be

  1. M’s argument objects, including the self object or
  2. The instance variable objects of C.

(Objects created by M, or by functions or methods which M calls, and objects in global variables are considered as arguments of M.)

And this all makes sense to me. Except for one part (paraphrasing to simplify):

Objects CREATED by methods which M calls are considered as arguments of M (adheres to 1st rule)

The paper also says this:

The Law prohibits the nesting of generic accessor function calls, which return objects that are not instance variable objects.

It allows the nesting of constructor function calls.

An accessor function is a function which returns an object which did exist before the function is called. A constructor function returns an object which did not exist before the function is called.

so then, the example below is considered adhering to LoD, if each method returns a new instance of some kind of object ?

function text(BookClass book) {
    book.pages().last().text()
}

but doesn't adhere to the law if those same methods return existing instances of objects / primitive values ? it doesn't matter what kind of object is returned, just as long as it's created in the called method ? so then where is the loose coupling, information hiding and the other benefits, if this adheres to the law of Demeter ?

I feel like i'm missing something here.

1

There are 1 answers

10
jaco0646 On

The Law of Demeter is written to be language-agnostic, which accounts for the generic definitions of "accessor" and "constructor".

A given (OO) language typically has specific syntax for creating new Objects. For example, in Java, a "constructor function" is always preceded by the new keyword. So the generic description,

Objects created by M, or by functions or methods which M calls,

...allows M to make constructor calls using the new keyword in Java. Objects returned from these constructor calls, "are considered as arguments of M."

Here is the Law of Demeter in Java code, following the order in which the rules are described in the aforementioned paper.

class Class_C {
    TypeOne instanceVariable;

    void method_M(TypeThree argument) {
        // Allowed to call methods on argument variable types.
        // "1. The argument classes of M..."
        TypeFour typeFour = argument.getTypeFour();

        // Allowed to call methods on own (or inherited) types.
        // "1. The argument classes of M (including C)."
        allowed();

        // Allowed to call methods on own (or inherited) instance variable types.
        // "2. The instance variable classes of C."
        TypeTwo typeTwo = instanceVariable.getTypeTwo();

        // Allowed to create Objects and/or call constructor methods.
        // "Objects created by M or by functions or methods which M calls...
        //   are considered as arguments of M."
        String newString = "Hello, Law of Demeter!";
        List<String> newList = new ArrayList<>();

        // Allowed to call methods on global variables,
        //   e.g. println() on the 'out' variable of System.
        // "Global variables are considered as arguments of M."
        System.out.println(newString);

        // NOT allowed to call methods on other classes' instance variable types.
        // "The Law _prohibits_ the nesting of generic accessor function calls..."
        //   One accessor call was made to retrieve each of these variable types,
        //     so a second call would be inappropriate nesting.
        typeTwo.notAllowed();
        typeFour.notAllowed();

        // Allowed to call methods on newly-constructed objects.
        // Remember, these "are considered as arguments of M."
        // "[The Law] _allows_ the nesting of constructor function calls."
        //   One constructor call followed by a second call is appropriate nesting.
        newList.add(newString);

        // Note: nested method calls on the _same_ variable type are allowed,
        //   because this does not change the implications of rules 1 and 2.
        argument.aMethodReturningTypeThree().anotherMethodReturningTypeThree();
        instanceVariable.aMethodReturningTypeOne().anotherMethodReturningTypeOne();
    }

    void allowed() {}
}

It would be easier to read the LoD if it was written separately for each language using only syntax specific to that language. But to describe a general principle the authors combined terminology. The term "constructor" means different things in different languages. To apply the principle to your language, you must substitute the syntax defined by your language. A commonality across languages is that a constructor "returns an object which did not exist before..." In addition, a constructor is defined by the syntax of your language. A method may call this syntax and nest/chain calls onto the new Object.

For example, my language is Java. In Java, the only syntax for a constructor looks like: new Object(). This function call does two things.

  • It satisfies the common description from the LoD by returning an object which did not exist before.
  • It satisfies the definition of a constructor, in my language.

Now, having a new object isn't very useful to my method unless I call methods on that new object. So the LoD specifically "allows the nesting of constructor function calls" meaning I can chain something like: new Object().toString() which would not be allowed if new Object() was a normal, non-constructor function.

The phrase, "allows the nesting of constructor function calls" means any number of methods can be called on the new object returned from a constructor. Since these additional methods are not constructor function calls themselves, nesting calls onto them is not allowed. In other words, the following can all be called by M.

  • new Object().secondObject()
  • new Object().thirdObject()
  • new Object().fourthObject()

But M cannot call new Object().secondObject().thirdObject() because secondObject() is not a constructor function call (in my language) and therefore does not allow nesting/chaining. The LoD allows nesting on constructor function calls. It does not allow nesting on all function calls.