CSC/ECE 517 Fall 2012/ch2a 2w25 nr

From Expertiza_Wiki
Revision as of 17:30, 30 October 2012 by Rnedunc (talk | contribs) (→‎Disadvantages)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Law of Demeter

Introduction

Law of demeter(LoD) states "Only talk to your friends", which means do not talk to friend's friend. By doing this, we don't get to know the internals of our friend. In software terms, a class A should only talk to or invoke methods on closely related classes, class B in this case and should not try to poke around the internals of B to reach a new class C. We have two options in order to achieve this, either class A can directly invoke methods on class C or the interface of class B should be changed to accomodate this call from A, then A will invoke methods on B and B will take care of invoking methods on C internally that would satisfy A's request.

In a way, law of demeter can be thought of a more specialized version of low coupling principle. Object oriented design recommends low coupling for better design and maintainability of software systems. The more loosely the classes are coupled, they become more easily testable. Classes need to directly interact with its collaborators and be shielded from understanding their internal structure. Using the internal knowledge of classes introduces risk of breaking functionality, instead they should talk to well known public interfaces. By doing all this we try to create a more shallow relationship than deeper relationship, which reduces the number of classes which may be affected by changes within the system.

Who can class A interact with?

If M is a method belonging to class A, then M may invoke other methods belonging to the following<ref name="">Who can class interact with</ref>:
1. Class A
2. Members of A
3. Parameters of A
4. Objects created by M
5. Objects created by other methods of class A called by M
6. Globally accessible objects

When and How to identify Law of Demeter

From the above sections we have a good understanding of the law and its benefits, but we haven't yet discussed on how to identify places in existing code where we can apply it and just as important, where not to apply it. The following are the places where we can apply the Law of Demeter

Chained 'get' statements

The below statement clearly denotes we are violating the law of demeter since we our access crosses the boundary.

value = object.getName().getCountry().getZip()

More number of dots is also an indicator of the violation of law.

Lots of 'temporary' objects

name = object.getName();
country = name.getCountry();
zip = country.getZip();

This is equivalent to chained get statement, but broken down into multiple statement, this is little more harder to detect that previous case, but this also clearly violates the law

Importing or including many classes

In Java, there is a rule which states that we only import classes that are actually used. These practises are never followed and we always see something like below in our source code.

import java.lang.*
import java.io.*;
import java.util.*;

With this rule in place, it is not uncommon to see a dozen or so import statements all coming from the same package. If this is happening in our code, it could be a good place to look for obscured examples of violations. If we need to import it, we are coupled to it. If it changes, we may have to change as well. By explicitly importing the classes, we will begin to experience how coupled the classes really are.

Advantages

The advantages of following Law of Demeter are<ref name="Advantages">Advantages</ref>:
Reusable code
Better design
Less risk of breaking functionality
Modular code

Disadvantages

In order not to expose the internals of the calling class, many layers of wrapper needs to be added. This introduces overhead in maintaining the wrapper and might introduce significant delay since control has to follow all the wrappers<ref name="Disadvantages">Disadvantages</ref>.

Example

Java

Law of Demeter is a technique for reducing the 'coupling' between objects in the code. Here is an example based on Java that illustrates the Law of Demeter<ref name="The Paperboy, The Wallet,and The Law Of Demeter in Java">The Paperboy, The Wallet,and The Law Of Demeter in Java</ref>.

A ‘Paperboy’ has to get a payment from a ‘Customer’. There has to be a mechanism used by the paperboy for receiving payments from that customer. Lets assume that the paperboy will run a snippet in order to achieve the above. The definition of each Java class for this example is as follows.

Customer

The Customer class can have a first name, a last name, an account number, a shipping address, some method of payment, etc. In this example lets define the customer with just enough functionality to make our illustration of Law of Demeter to work.

public class Customer {
    private String firstName;
    private String lastName;
    private Wallet myWallet;
    public String getFirstName(){
        return firstName;
    }
    public String getLastName(){
        return lastName;
    }
    public Wallet getWallet(){
        return myWallet;
    }
}

Wallet

Each customer is associated with a wallet by which the payment is made. A wallet can have total money, credits cards, drivers license, different types of currency, etc. For this example we design a pretty simplistic wallet.

public class Wallet {
    private float value;
    public float getTotalMoney() {
        return value;
    }
    public void setTotalMoney(float newValue) {
        value = newValue;
    }
    public void addMoney(float deposit) {
        value += deposit;
    }
    public void subtractMoney(float debit) {
        value -= debit;
    }
}

Now that we have all required information about the customer and his wallet. Lets turn our focus on to the paperboy.

Paperboy

In this paperboy class we focus more on the method by which payment is made. In order to get the payment from the customer the paperboy executes a code snippet. The code snippet is some method in the Paperboy class (eg : getPayment() ) which achieves this objective. The typical getPayment method for this illustration is as follows

Paying the Paperboy - getPayment()

void getPayment(Customer myCustomer) {
   payment = 2.00;  // Payment amount
    Wallet theWallet = myCustomer.getWallet(); // Wallet of the corresponding customer who has to pay
    if (theWallet.getTotalMoney() > payment) {
        theWallet.subtractMoney(payment);
    } else {
        // Other code
    }
}

This code snippet gets the wallet from the customer, checks to make sure if the customer has the money and transfers it from the customers wallet into the paperboy's wallet.

Lets analyse what is bad about this code. We can actually translate what the code is actually doing into real-language. Apparently, when the paperboy stops by and demands payment, the customer is just going to turn around, let the paperboy take the wallet out of his back pocket, and take out two bucks. This is not the typical how some will handle their wallet.

There are a number of 'real-world' problems with this, not to mention we are trusting the paperboy to be honest and just take out what he's owed. If our future Wallet object holds credit cards, the paperboy has access to those too. The basic problem is that “the paperboy is being exposed to more information than he needs to be”.

The most important concept is the 'Paperboy' class now 'knows' that the customer has a wallet and can manipulate it. When we compile the Paperboy class, it will need the Customer class and the Wallet class. These three classes (Customer, Wallet and Paperboy) are now 'tightly coupled'. If we change the Wallet class, we may have to make changes to both of the other classes.

There is another classic problem that this can create. The customer wallet can be stolen or lost. In this case we can set the Wallet to null like

victim.setWallet(null);

This seems a reasonable assumption. This code enforces any mandatory value for wallet and there is a certain 'elegance' about using null for this condition but this never handles the paperboy. The code assumes there will be a wallet so the paperboy will get a runtime exception for calling a method on a null pointer. We could fix this by checking for 'null' on the wallet before we call any methods on it, but this starts to clutter the paperboy's class. The real-language description is becoming even worse as more and more complex arises. This makes the design and code more convoluted.

Code improvement , better design using Law of Demeter

The example illustrated can be made much better by incorporating proper design and improving the code structures. The proper way to fix the issue to move along the 'real world' scenario. When the paperboy needs payment, he would approach the customer but he is not going to handle the customer wallet. In fact he won't even know whether the customer has a wallet or not even if he does, he can’t be sure that the customer is willing to pay him.

The New Customer

The New customer has design changes such that he no longer has a 'getWallet()' method but it does have a getPayment method. The new customer class is as shown below

public class Customer {
    private String firstName;
    private String lastName;
    private Wallet myWallet;
    public String getFirstName(){
        return firstName;
  }
    public String getLastName(){
        return lastName;
    }
    public float getPayment(float bill) {
        if (myWallet != null) {
            if (myWallet.getTotalMoney() > bill) {
                theWallet.subtractMoney(payment);
                return payment;
            }
        }
    }
}

The New Wallet

There won’t be any changes in design or the code of the Wallet as it performs the required function without any hassle in this example.

The New Paperboy

In this new paperboy class we have a method for getting the payment but this done by customer and not the right of the paperboy as per the previous design. In order to get the payment from the customer the paperboy executes a code snippet. The code snippet is some method in the Paperboy class (eg : requestPayment() ) which achieves this objective. The typical requestPayment method for this illustration is as follows

void RequestPayment(Customer myCustomer) {
   payment = 2.00;  // Payment amount
   paidAmount = myCustomer.getPayment(payment);
   if (paidAmount == payment) {
        // Give customer a receipt
    } else { 
        // Other code
}

This had made the code and design much better.

Reasons that led to better design and code

The first reason that this is better is because it better models the real world scenario. The Paperboy code is now 'asking' the customer for a payment. The paperboy does not have direct access to the wallet.

The second reason that this is better is because the Wallet class can now change and the paperboy is completely isolated from that change. If the interface to Wallet were to change, the Customer would have to be updated, but that's it. As long as the interface to Customer stays the same, none of the client's of Customer will care that he got a new Wallet. Code will be more maintainable, because changes will not 'ripple' through a large project.

The third and probably most 'object-oriented' answer is that it is now free to change the implementation of 'getPayment()'. In the old example, the assumption was that the Customer would have a wallet. This led to the null pointer exception. In the real world though, when the paper boy comes, the customer may actually get the two bucks from a jar of change, search between the cushions of his couch or borrow it from his roommate, whatever. All of this is 'Business Logic',and is of no concern to the paper boy. All this could be implemented inside of the getPayment() method, and could change in the future, without any modification to the paper boy code.

Those these above discussion have led to lots of advantages we need to accept the fact that it has made the customer a more complex object.

Ruby

While developing software, Law of Demeter are violated often especially in Ruby on Rails which allows to easily navigate between objects based on table relationships<ref name="Law of Demeter in Ruby on Rails">Law of Demeter in Ruby</ref>.

For example, if you are working on a partial that displays a charge from a credit card statement it wouldn't be surprising to see:

charge.statement.customer.name

The above code could be used to display the customer's name in the partial. Unfortunately, it assumes statement will have a customer and customer will have a name. This is easily fixed by changing charge to only talk to it's friends.

charge.customer_name

The simple fix to support this is to define customer_name in charge, and customer_name in statement

class Charge
 def customer_name
   statement.customer_name
 end
end

class Statement
 def customer_name
   customer.name
 end
end


This change is simple enough. However, as the list of methods that require delegation grows your class can become littered with delegation code <ref name="Understanding the Law of Demeter in Ruby"> Understanding the Law of Demeter in Ruby </ref>.

Fortunately Forwardable is included in the standard library of Ruby. Forwardable allows you delegate method calls to an object, on a method by method basis.

Using Forwardable the above code becomes:

class Charge
 extend Forwardable
 def_delegators :statement, :customer_name
end

class Statement
 extend Forwardable
 def_delegator :customer, :name, :customer_name
end

Forwardable becomes even more valuable when you need to delegate several methods def_delegators :amount_info, :units, :fractions, :currency.

References

<references/>

External Links

  1. Law of demeter(LoD) Law of Demeter
  2. Controlling the Complexity of Software Designs
  3. Loose Coupling with Demeter
  4. The Paperboy, The Wallet,and The Law Of Demeter