Question

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?

Was it helpful?

Solution

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:

  1. that m_A has an instance of the object B exposed via GetObject(); and
  2. that the object B 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:

  1. that 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.

OTHER TIPS

The difference stands out a bit more when you look at unit tests.

Assume DoSomething() has a side-effect that you don't want to happen in your test code because it would be expensive or annoying to simulate, something like a database access or network communication for example.

In the first case in order to replace DoSomething() in your test you need to fake both ObjectA and ObjectB and inject the faked instance of ObjectA into the class containing Func().

In the second case you just call Func() with the fake ObjectB instance which greatly simplifies the test.

To directly answer your question:

Version 2 produces more loosely coupled classes because Func in the first case depends on both the interface of the class of m_A and the class of the return type of GetObjectB (presumably ObjectB), while in the second case it only depends on the interface of class ObjectB.

That is, in first case, there's coupling between m_A's class and Func, in the second case, there isn't. If the interface of that class should ever change to not have GetObjectB(), but e.g. to have GetFirstObjectB() and GetSecondObjectB(), in the first case you'll have to rewrite Func to call the appropriate replacement function (and maybe even add some logic which one to call, maybe based on an additional function argument), while in the second version you can leave the function as it is and let the users of Func care about how to get that object of type ObjectB.

It is more flexible to changes. Imagine that m_A is an instance of an object A, developed by programmer Bob. If he decides to make a change in his code so that A no longer has a method to return an object of type B, then Alice, the developer of Func, would have to change her code too. Notice that you don't have this problem with the latter code snippet.

In software development this type of coupling results in what is called non-orthogonal designs, the kind of designs where you change a local part of the code somewhere and you need to change parts in other places as well.

Well I think that it should be obvious why the chaining of functions together is bad as it produces longer harder to maintain code. In the top example Func() is an ugly function because it seems like it would just be called like

Func();

Essentially telling you nothing about the function. The second proposed method calls the function with a B passed to it, which not only makes it more readable but means that you can write a Func() for other classes without renaming it (since if it takes no parameters you can't rewrite it for another class). That tells you that Func() will do similar things to the object even if the class is different.

To answer the last part of your question the loose coupling is achieved because the first example implies that you have to get a B through A which couples the classes together, the second example is more general and implies that the B can come from anywhere.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top