In full form it tells that a method of particular object can call only methods that belong to:
- the same object,
- any object that is an attribute of this object,
- any object that was passed as a method’s parameter
- any object that was locally created.
Ok, we know the definition. Now let’s check whether it make any sense? What are the consequences of the violation the LoD? What benefits this law bring us?
talking to myself
When we are invoking a method of the object it means that inside the method we need to have the possibility of using other object’s methods and its attributes, regardless of their accessibility. After all, it is one coherent whole which should work as a whole.But let’s assume for a moment, that our method cannot do it. It cannot interact with other methods or attributes of the object. What are we talking about in this case? If a method could use only public API of particular object, then we would stop thinking about it as a part of a whole. In that case the method would depend on this object. It would be dependency on the object, not belonging to it.
It’s worth keeping in mind the information that observation gives us. When a method does not use any attributes nor methods of the particular object, we should consider extracting this method from the object. Maybe it’s nothing more than violation of Single Responsibility Principle?
everything I will get
Some methods require passing an input parameters. It allows them to do exactly what they were designed for e.g., mathematical operations.If there were no possibility to create methods with parameters, then we would end with a bunch of God Objects that would have to contain each method that is required to provide expected functionality. Methods that aren’t a part of any common whole.
Another repercussion of this would be an incredible amount of duplicated code. Each object would have to contain everything that is needed to do what is required.
Probably in world like this Ctrl-C+Ctrl-V would be our favourite pattern :P
in the particular context
Let’s move to the third point.Sometimes we just have to store partial results of our calculation somewhere. Of course we can live without some /variables. Not all of them are really required, but even those have one important purpose - they increase the readability of our code.
friend of my friend is my…?
When I wrote about the definition of LoD I mentioned that we should contact only with “the closest friend”. It means that in code we should use only methods of objects that are listed above.And this is the right time to talk about accessors which definitely are methods, but… why we shouldn’t use them?
If we need to communicate with one of the attributes of the object that we’re interacting with it’s equal with taking out from it detailed information about its implementation. We know that we should depend on behavior and not implementation, right? Ok, we know that, but is it still valid outside classroom? What real problem does looking into the guts of the objects bring?
We can define contract that needs to be satisfied by any object that will be passed to the method. Using type is our guarantee. Yet, it’s more complicated when we start to reach deeper, for object’s attributes. How we can be sure that all further dependencies will provide desired API?
That’s why instead of:
sebastian.getSkills().contains(OOP);it’s better to write:
sebastian.hasSkill(OOP);
It’s even worse when our dependencies are part of the longer chain of methods invocations:
sebastian.getSkills().get(OOP).increase();
What can you tell about the amount of effort needed to refactor or change it? Definitely it won’t be as easy as doing it based on the previous example.
That’s great, but what would happen in situations where we don’t depend on interface, but on a specific class? When this is the case, we are strictly showing that we are depending not only on behavior, but also on implementation.
When you are using knowledge about the way how used component is built you are making dependency between your objects even stronger. Until their relations are based on behavior it’s easy to replace one implementation with another. If we are using knowledge about internal structure this replacing is no longer easy.
Additionally, using attributes of those objects (through accessors or direct access) increases the number of your object’s dependencies. However, the worst thing is those dependencies are hidden. We will know about them only if we will read code of the method. Not by looking at its API.
And last, but not the least. When you obey this law, you automatically increase the readability of your code.
Take one more look at examples from this paragraph. Which are the most readable? Which are the least?
This has direct impact on the amount of effort that would be needed whenever you will have to get back to the code and change it. Fewer things that require your analysis and less “jumping” around the classes.
do you care about relations?
If you want to create methods that will be in relation with many objects, in relations that can partly be hidden from object users, then the violation of LoD is one of the best ways to reach this goal.But you have to remember that having so many “friends” requires huge effort and makes your code more fragile, because many parts will have direct impact on your object’s structure and behavior.
No comments:
Post a Comment