Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do the Law of Demeter and composition with collections work together?

I have read nearly all of the questions tagged Law-of-Demeter. My specific question is not answered in any of these other questions, though it is very similar. Mainly my question is when you have an object with layers of composition, but the need to retrieve property values from the various objects, how do you achieve this and why take one approach over another?

Let's say you have a pretty standard object composed of other objects, like this:

public class Customer {
  private String name;
  private ContactInfo primaryAddress;
  private ContactInfo workAddress;
  private Interests hobbies;
  //Etc...

  public getPrimaryAddress() { return primaryAddress; }
  public getWorkAddress() { return workAddress; }
  public getHobbies() { return hobbies; }
  //Etc...
}

private ContactInfo {
  private String phoneNumber;
  private String emailAddress;
  //Etc...

  public getPhoneNumber() { return phoneNumber; }
  public getEmailAddress() { return emailAddress; }
  //Etc...
}

private Interests {
  private List listOfInterests;
}

The following would both violate the Law of Demeter:

System.out.println("Phone: " + customer.getPrimaryAddress().getPhoneNumber());
System.out.println("Hobbies: " + customer.getHobbies().getListOfInterests().toString());

This also would violate the Law of Demeter, I think (clarification?):

ContactInfo customerPrimaryAddress = customer.getPrimaryAddress();
System.out.println("Phone: " + customerPrimaryAddress.getPhoneNumber());

So presumably, you would then add a "getPrimaryPhoneNumber()" method to Customer:

public getPrimaryPhoneNumber() {
  return primaryAddress.getPhoneNumber();
}

And then simply call: System.out.println("Phone: " + customer.getPrimaryPhoneNumber());

But doing this over time seems like it would actually provide a lot of problems and work against the intention of the Law of Demeter. It makes the Customer class into a huge bag of getters and setters that has way too much knowledge about its own internal classes. For instance, it seems possible that the Customer object will one day have various addresses (not just a "primary" and a "work" address). Perhaps even the Customer class will simply have a List (or other collection) of ContactInfo objects rather than specific named ContactInfo objects. How do you continue following the Law of Demeter in that case? It would seem to defeat the purpose of the abstraction. For example, this seems reasonable in such a case where a Customer has a List of ContactInfo items:

Customer.getSomeParticularAddress(addressType).getPhoneNumber();

This seems like it can get even crazier when you think about some people having a mobile phone and a landline phone, and then ContactInfo has to have a collection of phone numbers.

Customer.getSomeParticularAddress(addressType).getSomePhoneNumber(phoneType).getPhoneNumber();

In which case, not only are we referring to objects within objects within objects, but we also have to know what the valid addressType's and phoneType's are. I can definitely see a problem with this, but I am not sure how to avoid it. Especially when whatever class is calling this, probably does know that they want to pull the "mobile" phone number for the "primary" address of the customer in question.

How could this be refactored to comply with the Law of Demeter and why would that be good?

like image 775
Scott Shipp Avatar asked Mar 28 '13 21:03

Scott Shipp


People also ask

What is the Law of Demeter attempting to achieve?

The Law of Demeter principle reduces dependencies and helps build components that are loose coupled for code reuse, easier maintenance, and testability. The Law of Demeter (or the Principle of Least Knowledge) is a design guideline for developing software applications.

What is the Law of Demeter trying to prevent?

The Law of Demeter asks us to minimize coupling between classes and avoid reaching out to the third object in order in order to make refactoring and developing new features easily.

Which of these code examples violates the principle of least knowledge or Law of Demeter?

getObjectC(). display() this sort of statement is a violation of Law of Demeter. There are primarily 4 principles of the least knowledge in java as follows: Method M of an object O can invoke the method of O itself.

Which proposed the Law of Demeter?

The law dates back to 1987 when it was first proposed by Ian Holland, who was working on the Demeter Project.


2 Answers

It's important to remember that the Law of Demeter is, despite its name, a guideline and not an actual law. We need to examine its purpose at a slightly deeper level to determine what is the right thing to do here.

The purpose of the Law of Demeter is to prevent outside objects being able to access the internals of another object. Accessing internals has two problems: 1) it gives too much information about the internal structure of the object, and 2)it also allows outside objects to modify the internals of the class.

The correct response to this issue is to separate out the object being returned from the Customer method from the internal representation. In other words, instead of returning a reference to the private internal object of type ContactInfo, we instead define a new class UnmodifiableContactInfo, and have getPrimaryAddress return UnmodifiableContactInfo, creating it and filling it in as necessary.

This gets us both the benefits of the Law of Demeter. The returned object is no longer an internal object of Customer, which means that Customer can modify its internal storage as much as it likes, and that nothing we do to UnmodifiableContactInfo affects the internals of Customer.

(In reality I would rename the internal class and leave the external one as ContactInfo, but that's a minor point)

So this achieves the objectives of the Law of Demeter, but it still looks like we are breaking it. The way i think of this is that the getAddress method is not returning a ContactInfo object, but instantiating it. This means means that under the Demeter rules we can access ContactInfo's methods, and the code you have written above is not a violation.

You have to note of course that although the 'violation of Demeter's Law' occurred in the code accessing Customer, the fix needs to be made in Customer. In general the fix is a good thing - providing access to internal objects is bad, whether or not they are accessed using more than one 'dot'.

A few notes. It's obvious that an over-strict application of Demeter's law leads to idiocies, forbidding for example:

int nameLength = myObject.getName().length()

being a technical violation which most of us do every day. Everybody also does:

mylist.get(0).doSomething();

which is technically a violation. But the reality is that none of these is a problem unless we actually allow outside code to affect the behaviour of the main object (Customer) based on the objects it has retrieved.

Summary

Here is what your code should look like:

public class Customer {
    private class InternalContactInfo {
        public ContactInfo createContactinfo() {
            //creates a ContactInfo based on its own values...
        }
        //Etc....
    }
   private String name;
   private InternalContactInfo primaryAddress;
   //Etc...

   public Contactinfo getPrimaryAddress() { 
      // copies the info from InternalContactInfo to a new object
      return primaryAddress.createContactInfo();
   }
   //Etc...
}

public class ContactInfo {
   // Not modifiable
   private String phoneNumber;
   private String emailAddress;
   //Etc...

   public getPhoneNumber() { return phoneNumber; }
   public getEmailAddress() { return emailAddress; }
   //Etc...
}

}

like image 43
DJClayworth Avatar answered Oct 24 '22 11:10

DJClayworth


In my experience, the Customer example shown is not a “standard object composed of other objects,” because this example takes the added steps of implementing its composing pieces as inner classes, and furthermore making those inner classes private. That’s not a bad thing.

In general the private access modifier increases information hiding, which is the basis for the Law of Demeter. Exposing private classes is contradictory. The NetBeans IDE actually includes a default compiler warning for, “Exporting non-public type through public API”.

I would assert that exposing a private class outside its enclosing class is always bad: it reduces information hiding and violates the Law of Demeter. So to answer the clarification question about returning an instance of ContactInfo outside of Customer: yes, that is a violation.

The proposed solution of adding a getPrimaryPhoneNumber() method to Customer is a valid option. The confusion is here: “Customer... has way too much knowledge about its own internal classes.” That’s not possible; and that’s why it’s important that this example is not a standard composition example.

An enclosing class has 100% knowledge of any nested classes. Always. Regardless of how those nested classes are used in the enclosing class (or anywhere else). That’s why an enclosing class has direct access to private fields and methods of its nested classes: the enclosing class inherently knows everything about them, because they’re implemented inside it.

Given the preposterous example of a class Foo, which has a nested class Bar, which has a nested class Baz, which has a nested class Qux, it would not be a violation of Demeter for Foo (internally) to call bar.baz.qux.method(). Foo already knows everything there is to know about Bar, Baz, and Qux; because their code is inside Foo, so there is no additional knowledge being passed via the long method chain.

The solution then, per the Law of Demeter, is for Customer not to return intermediate objects, regardless of its internal implemention; i.e. whether Customer is implemented using several nested classes or none, it should return only what its client classes ultimately need.

For example the last code snippet might be refactored as, customer.getPhoneNumber(addressType, phoneType);

or if there are only a small number of options, customer.getPrimaryMobilePhoneNumber();

Either approach results in users of the Customer class being unaware of its internal implementation, and ensures those users do not have to call through objects they're not directly interested in.

like image 195
jaco0646 Avatar answered Oct 24 '22 10:10

jaco0646