Working with modular software is something that not all of us can be blessed with. Code that is properly isolated and hidden behind abstraction seems to be a unicorn, that we heard about, but didn’t see it. Even if you did not see it, there is no argument not to chase it. The Law of Demeter can be something that keeps the track of your horny friend.
It is a not-so-fresh idea, as it was published in late ’80 on Northeastern University, but it still is relevant. The Law of Demeter defines, how objects interact with each other. It says that you should talk only to the object that you directly know. Sometimes you can hear about it as a “shy programming” or “don’t talk to strangers” rule.
The Law of Demeter in theory
Reasons, why you should follow the law, are not so visible in the beginning, but in time it will pay back with easier code maintenance. Law of Demeter simplifies methods by limiting the number of used types inside them. It promotes information hiding with proper abstraction and narrow interfaces. Its restriction for operating only on types that you directly have access to distils the knowledge and responsibility of every object. That separation simplifies changes needed to be done in future during code extension or maintenance.
The Law of Demeter defines only two requirements, but they need to be fulfilled by every method in the code. A single method can only operate on objects that are:
- passed as arguments to the method
- values of fields defined in this class
In addition to that, all globally available values, or objects created within methods, can also be used, as they are considered as method arguments.
What does it mean to not talk to strangers?
Simply, no method chaining! However, simplifying Law of Demeter to short version is not very precise. Still, it should be possible to use the fluent interface, but it should give a quick overview. To have a better understanding of that, let’s check our sample.
We are making an application, that reflects company structure. The company itself is composed of Departments, that are divided into Divisions. Each Division may have multiple Teams with few Members. As this is the end of a year, we need to prepare a report of current employee cost per department. Quick implementation of such feature fits in a few lines. It even looks very seriously, like some kind of very important code that business pays for.
Company class that uses
After a successful presentation of cost to the board, the CEO decides to flatten the company structure. It should reduce operating costs in the future. From now, there will be no Divisions, so Teams will be located directly under Departments. To reflect the real-world structure, we need to update our code accordingly.
Updated structure of a company reflected in the code.
Now, in our sample, change in some nested class structure caused a change in top-level class. The situation, when we are accessing an object through another one causes an explosion of places that needs an update. Instead of changing only objects, that were directly reflecting
Division, we need to update also all those that were using it through other objects. As a consequence, we need to do major code review and rewrite in every place that was somehow related to
Division class. This is the exact case which the Law of Demeter tries to protects us from, allowing our software to grow constantly.
Now, in the whole project, in case if we are chaining objects, that might lead to a lot of places to change. How we can avoid chaining?
Obeying the Law of Demeter
Fulfilling the Law of Demeter is strictly connected with Object-Oriented Principles. Keeping proper object abstraction and hiding internals with encapsulation prevents unwanted chaining of method calls. It will be easier while writing new code, but here is also a workaround for legacy one.
Designing code with the Law of Demeter in mind
While writing new code, we should only use objects that are directly available. It means only the ones that are defined within the current class, provided as arguments to the method, or are globally available in the application. In Company class, we store a list of
Department, so we should only invoke methods from this class. How to calculate the cost for a department, when we need to know how much does Team Members cost, but we can only invoke methods on
Department itself? From the code point of view, we should not even know that there are Teams under Department. We need to delegate it.
After delegating cost calculation the
Company class is simplified a lot. The same thing should be done on
Having cost calculation delegated to nested object, we are significantly reducing cyclomatic complexity. Keeping it low simplifies testing, increases readability and eases maintenance.
Dealing with legacy code
You will find a lot of reasons during development to not obey the Law of Demeter, especially when working with legacy code. “It was already like this when I found it” and “I do not want to break anything” are my favourite excuses. Other ones, like you are not the one who designed the object, but only consuming its API, or that making a change will require to update gazillion of files to comply with the Law, are usually more serious.
Taking baby steps during refactoring might be simpler. Take a note on that the Law of Demeter is saying about what method should know, but not about what class itself is aware of. We can use that to bend the rules a little bit to start refactoring. The starting point can be a self call to an internal method that will wrap nested invocations for us.
The Law of Demeter allows for an alternative solution when we cannot add new methods to Department.
Extracting methods can be the first step for refactoring legacy code. It will also be easier to understand by your team what is happening. While doing code reviews, instead of pushing all changes at once for review, you can split whole work into two phases. First, would be to extract a private method within a class that used to chain invocations. The second step would be to move the private method into the nested class and utilise it. Probably your IDE can do this refactoring for you.
Code smells that should bring your attention
Apart from extracting method to limit a lot of “dots” in the code, there are also other things that you should look at.
Type checks of nested objects
Chaining methods is not the only thing that breaks the Law of Demeter. Remember, that the law allows you only to talk with the object you know directly, therefore asking any other object, what it is, is a violation.
Instead of digging in Department details, it is better to ask, if a department is a cost centre.
Sometimes we are tempted to go with a shortcut, hoping no one will notice our cheat. Instead of method chaining to get an internal object, you might want to assign a returned value into a variable.
There is nothing bad in assigning returned value, but invoking methods on it is something to be ashamed of. It differs nothing from direct method chaining but is often applied when the Law of Demeter is misunderstood. There is also a specific case for this.
Direct value return
Similarly to the shameless assignment, in this scenario, you are also reusing value returned by some method. The only difference is that instead of storing the result of methods in another variable, it is returned directly.
Inheritance with Law of Demeter
Till now, we covered in details about how to handle methods, but what about fields? The second point of Law of Demeter states that we can work on values from fields of the current class. Depending on the way that you implement classes for the inheritance, you might follow the strong or weak implementation of the law.
Strong implementation of the law guarantees that only fields defined in the current class can be used. On the other hand, weak Law of Demeter allows you to use field values from the current class, and also inherited class values. Both solutions are providing some pros and cons. Strong law promotes encapsulation and localizing the information. The benefit is that subclasses are immune to structural changes made to the superclass. Using the weak implementation of the law gives us easier access to the data, and limits the number of methods needed to be written for new features.
The Law of Demeter is a rule that is worth keeping in mind, but I would not recommend to use it as dogma. Fulfilling it will significantly reduce code complexity and simplify testing. I prefer strong implementation of the law while developing core domain, but it has a drawback of that is an explosion of the number of methods that objects are exposing. For other parts of the application, we can relax a little bit and be more pragmatic.