Search code examples
law-of-demeter

Law of Demeter - The pragmatic programmer


I have some questions considering the exercises in "the pragmatic programmer".

It says:

1.

public void showBalance(BankAccount acct) {
Money amt = acct. getBalance() ;
printToScreen(amt .printFormat()) ;
}

The variable acct is passed in as a parameter, so the getBalance call is allowed. Calling amt.printFormat(), however, is not. We don't "own"amt and it wasn't passed to us.

But we do own amt right? It is declared in the method and the LOD states: When your method creates local objects, that method can call methods on the local objects.

Is this example breaking the LOD? The way I see it, it isn't?

2.

public class Colada {
private Blender myBlender;
private Vector myStuff;

public Colada() {
myBlender = new Blender();
myStuff = new Vector() ;
}

private void doSomething() {
myBlender.addlngredients(myStuff.elements());
}
}

Since Colada creates and owns both myBlender and myStuff, the calls to addIngredients and elements are allowed .

Now I don't understand why doSomething is allowed to make calls to myBlender and myStuff since it didn't create it.

3.

void processTransaction(BankAccount acct, int) {
Person *who;
Money amt;
amt.setValue(123.45);
acct.setBalance(amt);
who = acct .getOwner() ;
markWorkflow(who->name(), SET_BALANCE);
} 

In this case, processTransaction owns amt, it is created on the stack, acct is passed in, so both setValue and setBalance are allowed. But processTransaction does not own who, so the call who->name() is in violation.

So here it does declare who but it is not allowed to make calls to it. Perhaps I misunderstand the concept of "owns".

Can someone please clarify this?

Thanks


Solution

  • Let's take a look at the supposed contradictions one by one.

    1.

    public void showBalance(BankAccount acct) {
        Money amt = acct. getBalance() ;
        printToScreen(amt .printFormat()) ;
    }
    
    The variable acct is passed in as a parameter, 
    so the getBalance call is allowed. 
    Calling amt.printFormat(), however, is not. 
    We don't "own" amt and it wasn't passed to us.
    

    This statement is perfectly valid, since LoD states that while acct can be passed to showBalance() and showBalance() can access getBalance() due to having a direct reference to acct, it may not call any method on any instance of Money. This is because no object of type Money was passed to showBalance(), and it merely referred to it through a local accessor. This does not mean that the ownership of amt is now with showBalance().

    2.

    public class Colada {
        private Blender myBlender;
        private Vector myStuff;
    
        public Colada() {
            myBlender = new Blender();
            myStuff = new Vector() ;
        }
    
        private void doSomething() {
            myBlender.addlngredients(myStuff.elements());
        }
    }
    
    Since Colada creates and owns both myBlender and myStuff, 
    the calls to addIngredients and elements are allowed .
    

    Now, what is happening in this class constructor is the declaration and instantiation of a Blender and a Vector object. Therefore, the owner of myBlender and myStuff, is the class Colada. LoD states that a method m of object o can access all direct components of o, so in this case, method doSomething() has access to Colada's components directly, and it may call methods of Blender and Vector on myBlender and myStuff.

    3.

    void processTransaction(BankAccount acct, int) {
        Person *who;
        Money amt;
        amt.setValue(123.45);
        acct.setBalance(amt);
        who = acct .getOwner() ;
        markWorkflow(who->name(), SET_BALANCE);
    }
    
    In this case, processTransaction owns amt, 
    it is created on the stack, acct is passed in, 
    so both setValue and setBalance are allowed. 
    But processTransaction does not own who, 
    so the call who->name() is in violation.
    

    The method processTransaction() receives the the bank account in object acct. It initializes an object of type Money, sets the value and then calls the setter method setBalance() on acct, which is in line with LoD. Since amt was created and initialized inside processTransaction, access to setValue of amt is also in line with LoD. Now comes the contradiction. *who is merely a pointer to an object of type Person, which is only visible through the accessor method getOwner. But that method is owned by acct, so calling a method on *who is invalid, as it breaks LoD.

    In short, LoD states that a.getB().getC().doSomething() is invalid, and only a.getB() is valid. If I have to write LoD in plain English, it can be specified in 3 words - Chain Of Command.

    Assuming a hierarchy where Object A contains an instance of Object B, and Object B contains an instance of Object C, the following hold true according to LoD.

    • Object A cannot access and change Object C through an instance of Object B.
    • Object B can, however, access and change Object C based on some criteria that it can fetch from Object A.

    I hope that clears your doubts.