I am curently reading the book
The Pragmatic Programmer, which was recomanded on
Eric Gunnerson blog. I have just read a section about
The Law of Demeter and realised that I often break it.
This law says that a class method should only call methods of: (1)
this
object, (2) objects passed in as parameters, and (3) objects locally created (including on the stack). This means that code like
obj.f().g()
or
A* objA = objB.GetA(); objA->DoIt()
are breaking the law. The authors suggest two possible solutions.
- [look at first example] If
obj
is passed in as a parameter then it might be possible to simply pass in obj.f()
and simply call g()
. This might not be possible in some circumstances, for example if you also need obj.h()
. In these cases you...
- [look at second example] Write a wrapper function
DoIt()
in the class of objB
that simply forwards the request to objA
. This way you can write simply objB.DoIt()
.
Why bother? Well, from a theoretical point of view you minimize the coupling between modules. Why? Well, let's look at the second example. The class of
objB
looks like this:
class B {
// ...
public:
A* GetA() const;
};
In other words it is already coupled to class A since it contains a function that returns a pointer to A. Adding a wrapper
DoIt
method does NOT increase the number of classes on which class B depends.
// B.hpp
class B {
// ...
public:
A* GetA() const;
void DoIt() const;
};
// B.cpp
void B::DoIt() const
{
GetA()->DoIt();
}
However writing such a wrapper will decrease the number of classes on which the client of class B depends. In the original version it depended on class B and class A. With the
DoIt
wrapper in place it depends only on class B.
But writing such wrappers is cumbersome and boring. Does it really pay off? I think that it is a situation very similar to writing properties (C#) or acessors (Java/C++) and accessing members thru them. If you found situations in the past when you said "Why didn't I used an accessor here?" then you are likely to find the Law of Demeter useful.
Avoiding call chains like
obj.f()->g()[3].h()
makes the code more readable. It also brings down compilation times.
I wish there were some tools to help with this by automatically doing the refactoring illustrated above.
1 comment:
I guess the short tip is: "Keep includes/imports at a minimum."
Post a Comment
Note: (1) You need to have third-party cookies enabled in order to comment on Blogger. (2) Better to copy your comment before hitting publish/preview. Blogger sometimes eats comments on the first try, but the second works. Crazy Blogger.