In his book API Design for C++, Martin Reddy elaborates on the Law of Demeter. In particular, he states that:
you should never call a function on an object that you obtained via another function call.
He supports his statement with a chaining function calls like
Func()
{
[...]
m_A.GetObjectB().DoSomething();
[...]
}
Instead, he encourages to pass B as an argument to the function like:
Func(const ObjectB &B)
{
[...]
B.DoSomething();
[...]
}
My question: why the latter example would produce more loosely coupled classes than the former one?
The analogy often used (including on the Wikipedia page, I notice) is that of asking a dog to walk — you'd ask the dog, you wouldn't ask for access to its legs and then ask its legs to walk.
Asking the dog to walk is a better decoupling because one day you might want a dog with something other than legs.
In your specific example, m_A
's implementation may cease to depend on an instance of B
.
EDIT: as some people want further exposition, let me try this:
If the object X
contains the statement m_A.GetObjectB().DoSomething()
then X
must know:
m_A
has an instance of the object B
exposed via GetObject()
; andB
has the method DoSomething()
.So X
needs to know the interfaces of A
and B
, and A
must always be able to vend a B
.
Conversely, if X
merely had to do m_A.DoSomething()
then all it needs to know is:
m_A
has the method DoSomething()
.The law therefore aids decoupling because X
is now fully decoupled from B
— it needs have no knowledge of that class whatsoever — and has less knowledge about A
— it knows that A
can achieve DoSomething()
but it no longer needs to know whether it does that itself or whether it asks somebody else to do it.
In practise the law is often not used because it usually just means writing hundreds of wrapper functions like A::DoSomething() { m_B.DoSomething(); }
, and the formal semantics of your program often explicitly dictate that A
will have a B
so you're not so much revealing implementation details by supplying GetObjectB()
as you are merely fulfilling that object's contract with the system as a whole.
The first point can also be used to argue that the law increases coupling. Supposing you originally had m_A.GetObjectB().GetObjectC().GetObjectD().DoSomething()
and you'd collapsed that down to m_A.DoSomething()
. That means that because C
knows that D
implements DoSomething()
, C
must implement it. Then because B
now knows that C
implements DoSomething()
, B
must implement it. And so on. In the end you've got A
having to implement DoSomething()
because D
does. So A
ends up having to act in certain ways because D
acts in certain ways whereas previously it might have had no knowledge of D
whatsoever.
On the first point a comparable situation is Java methods traditionally declaring the exceptions they can throw. That means that they also have to list the exceptions that anything they call may throw if they don't catch it. So every time a leaf method adds another exception you have to walk up the call tree adding that exception to a whole bunch of lists. So a good decoupling idea ends up creating endless paperwork.
On the second point I think we stray into the 'is a' versus 'has a' debate. 'Has a' is a very natural way to express some object relationships and dogmatically hiding that behind a facade of "I have the locker keys so if you want your locker open just come and ask me, and I'll unlock it"-type conversations just obscures the task at hand.