Any time I see articles about Law of Demeter the author never seems to give a solid example of how to obey this law. They all explain what it is and show an example of breaking
You are not applying LoD at the appropriate level: both Band
and Guitarist
should be considered a part of the same module and the dilemma you have shall be decided on the grounds of maximum convenience.
Your question is an example of a much wider problem, which I have frequently met in books on design patterns and similar: they try to explain wide-reaching principles, which concern the design of a complex system, on ridiculously undersized problems. The result is just reader's confusion.
Where you'd actually see this principle in effect is something like this: you are using AsyncHttpClient
, which is an abstraction built atop Netty
, which is an abstraction built atop Java NIO. Now, if AsyncHttpClient
's API forced you at some place to directly manage a Java NIO object, whose API is much more raw, and deals with concepts completely foreign to AsyncHttpClient
, that would be an example of breaking LoD.
I think the idea of the law is, as Dancrumb says above, to ensure that people are accessing objects at the appropriate level.
Imagine that our Band
class models the front desk of the band's offices. It's job is to act as a PA to the band members & deal with anyone who wants to interact with the band.
Now let's say we had a tour promoter from a PR company who wants to put together some PR material for their next tour. We could model him with a class:
class TourPromoter {
public String makePosterText(Band band) {
String guitaristsName = band.getGuitarist().getName();
String drummersName = band.getDrummer().getName();
String singersName = band.getSinger().getName();
StringBuilder posterText = new StringBuilder();
posterText.append(band.getName()
posterText.append(" featuring: ");
posterText.append(guitaristsName);
posterText.append(", ");
posterText.append(singersName);
posterText.append(", ")
posterText.append(drummersName);
posterText.append(", ")
posterText.append("Tickets £50.");
return posterText.toString();
}
}
In real life, this is the equivalent of the tour promoter ringing up the office & saying:
Tour Promoter: Can I speak to your guitarist?
Receptionist: OK, I'll get him for you.
Guitarist: Hello, this is the guitarist
Tour Promter: Hi, I'm putting together your latest poster. Just wanted to check your name?
Guitarist: It's Jimmy Page
Tour Promoter: Great, thanks. Oh, could you get me your drummer?…
Suffice it to say that the PA would get fired pretty quickly. Most reasonable people would ask "Couldn't you have handled that phone call for us?"
We could have a getGuitaristsName()
method, which would technically be honouring the Law of Demeter, but we're still asking our TourPromoter
class to remember details about the band — i.e. that they have a guitarist — whereas this information should really belong to the band itself.
To make sure we're introducing methods in a sensible way, we need to look at what the tour promoter is actually looking for — i.e. the names of all the band members. If we model that method in code, it gives us greater flexibility to make changes to the Band
later on, without having to even touch TourPromoter
:
public class Band {
private Singer singer;
private Drummer drummer;
private Guitarist guitarist;
public String[] getMembers() {
return {singer.getName(), drummer.getName(), guitarist.getName()};
}
}
public class TourPromoter {
public String makePosterText(Band band) {
StringBuilder posterText = new StringBuilder();
posterText.append(band.getName());
posterText.append(" featuring: ");
for(String member: band.getMembers()) {
posterText.append(member);
posterText.append(", ");
}
posterText.append("Tickets: £50");
return posterText.toString();
}
}
If we now add a Bassist or KeyboardPlayer, only the Band class needs to know the difference & the Tour Promoter doesn't need to change. This means that we're now also honouring the Single Responsibility Principle too — i.e. our makePosterText()
method only needs to change if we change our poster format, not if the band changes.
I don't think the Law of Demeter will tell you which method you need to pull out in order to meet the principle in the best way (e.g. getMembers()
rather than getGuitaristsName()
above) & in that way, I think you are right — it does show you when things are broken, but not necessarily how to fix them. Having the LoD in mind though, means that you keep an eye out for violations that can then be fixed through a combination of other design principles, like SRP.
The Law of Demeter itself doesn't present a methodology for good demand. At best, it's a useful heuristic for identifying potentially problematic areas of code. Deviations from the law can be considered a 'code smell'
It can have the tendency to widen the interfaces of your classes, since it results in proxy, or wrapper, methods to give you access to properties that are owned by internal objects. As a result, its advantages should be balanced against its disadvantages (of potentially creating unwieldy classes and module).
It is best applied as a tool to identify code that may need refactoring. However, a simple swap of methods from
a.x.getFoo()
to
a.getXFoo()
is following the letter of the law, without following the spirit (acknowledgements to @TedHopp for that point). Instead, you should be looking into what users are trying to do when they pierce a
's abstraction in order to get x
's foo
and define that method accordingly. It may not be a trivial task, but that's why we get paid the big bucks :)
I'd have a Band answer the question who's playing this instrument?
public String whoIsPlaying(String instrument);
Then if you need a guitarist's name you'd say:
band.whoIsPlaying("guitar");
Nothing stops you from pinging other instruments as well.