Introduction
At the moment, there are many time-proven practices that help developers write well-supported, flexible, and readable code. The law of Demeter is one such practice.
Since we are talking about this law in the context of writing good code, before reviewing it, I would like to express my understanding of what good code should be. After reading McConnell's “Perfect Code,” I firmly believed that the main technical imperative of software development is complexity management. Complexity management is a rather extensive topic, since the concept of complexity is applicable at any level of the project. You can talk about complexity in the context of the overall architecture of the project, in the context of the interconnection of the project modules, in the context of a separate module, a separate class, a separate method. But perhaps, most of the time, developers encounter complexity at the level of individual classes and methods, so I would formulate a general, simplified rule for managing complexity as follows: “The less things you need to keep in mind when looking at a separate piece of code, the less complexity code. That is, the program code must be organized so that you can safely work with individual fragments in turn (if possible without thinking about the other fragments).
Perhaps you will say that the most important thing in writing software is flexibility, the ability to quickly make changes. This is true, but, in my opinion, this is a consequence of what was written above. If there is an opportunity to work with individual code fragments, and the code is organized in such a way that, looking at it, you need to keep a minimum of other information in mind, most likely the introduction of changes will not be a pain.
Demeter's law is one of the recipes that help in dealing with complexity (and therefore with increasing flexibility of your code).
')
Demeter's Law
The law of Demeter tells us about the same thing that parents used to say in their childhood: “Do not talk to strangers”. And you can talk here with someone:
- With the methods of the object itself.
- With methods of objects on which the object depends directly.
- With created objects.
- With objects that come into the method as a parameter.
- With global variables (which I personally don’t think is true, since global variables greatly increase the overall complexity)
We will consider a specific example, and then we will draw conclusions about how the law of Demeter helps us in writing good code.
public class Seller { private PriceCalculator priceCalculator = new PriceCalculator(); public void sell(Set<Product> products, Wallet wallet) throws NotEnoughMoneyException { Money actualSum = wallet.getMoney();
The law is violated because of the object that comes to the method as a parameter (Wallet), we take another object (actualSum) and later call on it a method (isLessThan). That is, in the end, the chain is obtained: wallet.getMoney (). IsLessThan (otherMoney).
Perhaps you, like me, have a certain discomfort, looking at a method that takes a wallet as an argument and pulls money out of it.
A more correct version that satisfies the law would look like this:
public class Seller { private PriceCalculator priceCalculator = new PriceCalculator(); public Money sell(Set<Product> products, Money moneyForProducts) throws NotEnoughMoneyException { Money requiredSum = priceCalculator.calculate(products); if (moneyForProducts.isLessThan(requiredSum)) { throw new NotEnoughMoneyException(moneyForProducts, requiredSum); } else { return moneyForProducts.subtract(requiredSum); } } }
Now we transfer to the sell method a list of products to buy and money for these products. This code seems more natural and understandable; it improves the level of abstraction of the method:
sell(Set<Product> products, Wallet wallet) VS sell(Set<Product> products, Money moneyForProducts ).
Now this code has become easier to test. For testing, it is enough to create a Money object, whereas before it was necessary to create a Wallet object, then a Money object, and then put Money into a Wallet (perhaps the wallet object would be quite complicated and you would have to write mock'i, which would further increase total complexity of the test).
Perhaps the most important thing is that the pairing has decreased and the method has become much easier to use. Now the sale option does not depend on the availability of the wallet - it depends only on the availability of money. No matter where the money comes from - from a wallet, pocket or credit card, this method will not be tied to the "vault" of money.
When I read about the law of Demeter, it often seemed to me that the observance of other principles / tools for writing good code (SOLID, DRY, KISS, patterns, etc.) simply cannot lead to a situation when this law is violated.
For me personally, the law of Demeter is not "rule number 1". Rather, if this law is violated, for me it is a reason to think about whether I am doing everything correctly.
It is important that compliance with the law is not an end in itself, because the law itself does not have semantics, and blind observance of the law can only complicate the code. For example:
public class Seller { private PriceCalculator priceCalculator = new PriceCalculator(); public void sell(Set<Product> products, Wallet wallet ) throws NotEnoughMoneyException { Money requiredSum = priceCalculator.calculate(products);
Formally, each method satisfies the law of Demeter. In fact, he has all the same flaws that the very first option possessed (in the public method. Frequent advice that I met in such cases is to monitor not only the observance of the law, but also the number of class dependencies, the number of methods in a class and the number arguments in methods.
findings
Now let's summarize what kind of benefits we get in compliance with the law.
- Reduced code connectivity. It is achieved due to the fact that classes communicate only with their close relatives (with themselves, with method arguments and direct dependencies).
- Concealment of (structural) information. Thanks to the law of Demeter, we avoid situations where we pull out parts of the object (money from the wallet in our example) and manipulate them. After applying the law, we manipulate only these parts (only money). This allows you to avoid unnecessary knowledge, using only what we need (and usually improves the overall level of abstraction).
- Localization of information. Subject to the law, we exclude chains in the form of someClass.getOther (). GetAnother (). CallMethod (), limiting the circle of possible participants of communication. It helps much easier to navigate the written code, reducing the intellectual stress when reading the code.
- The duty of the classes becomes clearer. In almost any class that takes on too many responsibilities, there are either long call chains or a large number of class dependencies (and more often this and that). As a rule, observance of the law leads to the fact that one large class with multiple dependencies should be refactored into several small classes with a smaller number of dependencies and clearer responsibilities.
When can I not use the law of Demeter?
- When interacting with core jdk classes. For example, seller.toString (). ToLowerCase () formally violates the law of Demeter, but if it is used, for example, in the context of logging, there is nothing to worry about.
- In DTO-classes. The reason for the creation of DTO classes is the transfer of objects, and the chains of calling DTO class methods contradict the law of Demeter, but they fit into the idea of ​​DTO objects themselves.
- In collections. warehouses.get (i). getName () - formally also contradicts the law, but does not contradict the idea of ​​the collection.
- Use common sense;)