Let's try to consider a revised version of the LawOfDemeter. Here is the rationale for revising it, and the gist of the refactoring of the "law". Anybody can help with a sexy formula for the revised law? Please come with something at least as punchy as the original LawOfDemeter. --CostinCozianu
At first I jumped to say that LawOfDemeterIsInvalid. After all, if it was a valid law most of functional programs would have to be considered smelly or bad style, because all you do in FunctionalProgramming is navigate from the component of one structure to another, sometimes using recursion even. And there's the problem with very healthy looking lines of code like:
void printDetailsAbout(Customer c) { // ... System.out.println("zip: " + c.getAddress().getZipCode()) // or Address a= c.getAdress(); System.out.println("city: " + a.getCity()); System.out.println("state: " + a.getState()); // and so on, so forthAs per the original LawOfDemeter this code is found in violation of the law. But if we really are to judge this code as BadCode, first we'd have to fire all FunctionalWeenies, and lots of ObjectWeenies who often find themselves writing code as the above. And it works, and we don't get bitten by the dangers prescribed in LoD as punishments for breaking the law: like higher coupling, maintenance problem, difficulty in refactoring, etc. On top of that, all known "cures" for code like the code above can be judged objectively (that means we can count the beans!!!) as worse than the disease. Indeed there are naive solutions like adding a Customer.getCity(), Customer.getZip(), Customer.getAddressLine1() all they do, they increase the size of the interface for the Customer class, and while writing those forward methods you find yourself WritingDumbRepetitiveCode?. Uggh, it reminds it of the old joke: "Doctor, it hurts when I do this! -- Then don't do that."
There are more "advanced" fixes, like moving the code above into Customer.printDetailsOn(PrintStream? s), in other words TellDontAsk, which WardCunningham himself blessed with "finally a version of the 'law' that I can believe in". But can TellDontAsk be usefully applied in many examples like the one above ? Arguably not, because again the cure for the law is worse than the disease. If we refactor the code above into a method like Customer.printDetailsOn(PrintStream? s) then the resulting code has the following properties
So try repeat after me: "be lazy, be very lazy". Laziness is an outmost virtue in writing software. EconomyOfExpression trumps LawOfDemeter anytime (all other criteria being equal). If TheSimplestThing is to write System.out.println(c.getAddress().getZipCode()), then JustDoIt. It also happens to be the right thing.
None of the above-listed problems occur with a well-designed interface however. Instead of the customer printing its string representation, it should be sufficient to dynamically synthesize its string representation for the benefit of external programs. That is, the view object can invoke print(c.asMailingLabelString()), or even, x := c.asCSV() and parse out the fields as appropriate. Strictly speaking, these are type-casting methods, converting from an instance of customer to an instance of a subtype of string in a type-safe manner. This keeps coupling to an absolute minimum while maintaining full cohesion, and does not violate TellDontAsk one iota, as individual fields are never queried.
Well, that's nice and dandy, quite an argument that blindly following the LawOfDemeter could get you in trouble, I said to myself. LawOfDemeterIsInvalid. Or is it ? Thanks to Daniel T. from UseNet comp.object for pointing out to me that all I did was not really to invalidate the law but coming out with more exceptions to the law, he gave me further clues with regards to both positives and negatives to the law. But if exceptions are so many and obvious then maybe the law is too general and needs to be revised. There has to be a grain of truth to the law if so many people defend it so fervently. On the other hand it was JonathanTang, I believe (please correct me Jonathan, that pointed out that the LawOfDemeter applies only to ObjectOriented designs and not to FunctionalProgramming design style. I first objected you cannot look at the same lines of code and judge it differently depending on the intentions and background of the programmer -- if it was a FunctionalWeenie or a OoWeenie?, and what about MultiParadigmWeenie. But this gave me further an essential clue. And what led me to what I believe is the resolution to the dilemma is good old KristenNygaard.
Let's ask ourselves: if we want to be able to break the law in examples like the one above. When we would not like for our clients to break the law against us ? Do not do unto others ... Well, one example that sprang immediately to my mind is the following. Imagine you have a GUI framework and your Dialog class has one event processing thread for each active Dialog. And then your client does this on you:
dialog.getEventProcessingThead().interrupt(); // Ouch -- that really hurt and all the hell can break looseWell, one may wonder why's the getEventProcessingThread() public. Nevermind, assume there is a good reason. it shouldn;t be public but life's more complicated than that (or else we wouldn't need LoD of all).It can be because because the poor modular features if Java -- imagine a key implementation class in a separate packae -- say, gui.PlatformHook? accesses the thread for a good reason through a dedicated interface from another package gui.widgets.Dialog and thus it can be no less visible than public.
And other countless examples can be found where the LawOfDemeter is obviously valid. What happens then ? We don't like when our clients get a handle and mess around with some complex mechanism that we have for important object. It then turns out that the thing that both identifies and distinguishes legitimate exceptions to LoD is that they fit into different categories of objects/code as per NygaardClassification.
If we look at objects as active entities that emulate entities as in a physical system (Nygaard's definition of OO) than for sure we do not like when a client context messes the internal mechanism of our objects. But on the other hand, we know from experience that we can't have all objects in a OO system be active, sometimes they are just PlainOldJavaObjects? (or PoJo?) that cannot be thought of as playing an active role in our OO design, but rather they are an expedient way to put things together. Object systems are typically a balanced mix of functional/procedural style data structures and active objects as per NygaardClassifications, even when in languages like Java we have the data structuires wrap up in fancy classes like CustomerAddress?. CustomerAddress? is no active entity in your OO design, it's just a convenient way of grouping together some information in order to get a better structure. And when you have such a class you really don't mind in fact you should not be bothered to do something about when a client context wants to print the ZipCode? formatted 10 spaces to the left or to the right.
So the main insight that extended discussion with folks on wiki and on UseNet about LawOfDemeter is that the distinction should be semantical, whereas the original LawOfDemeter made a mainly syntactical distinction (mainly against chaining x.m1().m2().m3() either directly or through intermediary local variables).
So semantically, knowing the design of the system, we can differentiate the two case
customer.getAddress().changeZipCode(newZipCode)or
customer.changeZip(newZipCode);Of course, a MultiParadigmWeenie may also like to put his FunctionalProgramming suit and create a new Address altogether, rather than change the fields of the contained object. I think the first form is to be preferred in order to avoid burdening Customer class with unwarranted responsibilites, but I'm not as strong about it as I am about methods that do not modify the state.
Uncooked, but that's never stopped me before...
Say only what you want others to know. Worry thyself not with why they would want to know it.
IsLawOfDemeterOverspecifiedOnCeeTwo
If we take the given example under the aspect of unit testing, the LawOfDemeter becomes more valid again.
It is true, that is is often less work to write
doSomethingWith(c.getAddress().getZipCode());than to implement a
Customer.getZipCode();and to write
doSomethingWith(c.getZipCode());But if we want to do unit testing for our code, we would have to prepare two mock objects Customer and Address, if we don't apply the LawOfDemeter. If we apply it, we only need to prepare one mock object.
El.
Why not add a VisitorPattern to the address class? So then you can write a visitor to convert the address to a string. So i end up calling something like:
addressString = c.getAddress().execute(addrToStrVisitor); System.out.print(addressString);
Benefits: - I can reuse the code from addrToStrVisitor whenever I need to print an address (rather than writing it over and over again) - I can extend the system with an abstract base address class to support different kinds of addresses (which is the whole point of Law of Demeter anyway), like foreign addresses where getZipCode doesn't even make sense.
If we break Law of Demeter here, then we're assuming that the address structure can never change, which I think is a terrible assumption actually.
-bdodson
// the phrase: // c.getAddress() // is a violation of LoD, because you're querying an attribute.Ah, fair enough. Perhaps i should have added the visitor to the customer class and said something like c.execute(addrToStrVisitor);
Nevertheless, I still think the point about localizability pokes a pretty big hole through the entire argument. -bdodson
The LawOfDemeter page specifies it in terms of only a parameter or field being valid as a receiver, and that you can invoke any method on a valid receiver, which means this would be a valid refactoring of the customer address example:
class CustomerAddressVisitor? implements Visitor<Customer> { private Visitor<Address> addressPrinter = ...; public void visit (Customer c) { addressPrinter.visit(c.getAddress()); } } class AddressPrinter? implements Visitor<Address> { private PrintStream? out = System.out; public void visit (Address a) { out.println("city: " + a.getCity()); out.println("state: " + a.getState()); } }What that might buy you is that it forces small methods with single responsibilities, and some separation of structure dependent methods and 'leaf' methods, though if you only transform paths to deeply nested calls you still have some coupling to structure. The alternative is to just use double dispatch and have the navigation internal to the customer. -- PeteKirkham