Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

There is the classic OOP problem of method chaining vs "single-access-point" methods:

main.getA().getB().getC().transmogrify(x, y)

vs

main.getA().transmogrifyMyC(x, y)

The first seems to have the advantage that each class is only responsible for a smaller set of operations, and makes everything a lot more modular - adding a method to C doesn't require any effort in A, B or C to expose it.

The downside, of course, is weaker encapsulation, which the second code solves. Now A has control of every method that passes through it, and can delegate it to its fields if it wants to.

I realize there's no single solution and it of course depends on context, but I would really like to hear some input about other important differences between the two styles, and under what circumstances should I prefer either of them - because right now, when I try to design some code, I feel like I'm just not using the arguments to decide one way or the other.

share|improve this question
add comment

6 Answers

up vote 20 down vote accepted

I think the Law of Demeter provides an important guideline in this (with its advantages and disadvantages, which, as usual, should be measured on a per case basis).

The advantage of following the Law of Demeter is that the resulting software tends to be more maintainable and adaptable. Since objects are less dependent on the internal structure of other objects, object containers can be changed without reworking their callers.

A disadvantage of the Law of Demeter is that it sometimes requires writing a large number of small "wrapper" methods to propagate method calls to the components. Furthermore, a class's interface can become bulky as it hosts methods for contained classes, resulting in a class without a cohesive interface. But this might also be a sign of bad OO design.

share|improve this answer
    
I forgot about that law, thank you for reminding me. But what I'm asking about here is mainly what the advantages and disadvantages are, or more accurately how should I decide to use one style over the other. –  Oak Feb 16 '11 at 11:21
    
@Oak, I added quotations describing the advantages and disadvantages. –  Péter Török Feb 16 '11 at 13:02
add comment

I in general try to keep the method chaining as limited as possible (based on the Law of Demeter)

The only exception I make is for fluent interfaces/internal DSL style programming.

Martin Fowler makes sort of the same distinction in Domain-Specific-Languages but for reasons of violation command query separation which states:

that every method should either be a command that performs an action, or a query that returns data to the caller, but not both.

Fowler in his book on page 70 says:

Command-query separation is an extremely valuable principle in programming, and I strongly encourage teams to use it. One of the consequences of using Method Chaining in internal DSLs is that it usually breaks this principle - each method alters state but returns an object to continue the chain. I have used many decibels disparaging people who don't follow command-query separation, and will do so again. But fluent interfaces follow a different set of rules, so I am happy to allow it here.

share|improve this answer
add comment

The Law of Demeter, as @Péter Török points out, suggests the "compact" form.

In addition, the more methods that you explicitly mention in your code, the more classes that your class depends on, increasing maintenance issues. In your example, the compact form depends on two classes, whereas the longer form depends on four classes. The longer form not only contravenes the Law of Demeter; it also will make you change your code every time you change any of the four referenced methods (as opposed to two in the compact form).

share|improve this answer
    
On the other hand blindly following that law means that the number of methods A has will explode with a lot of methods A may want to delegate away anyway. Still, I agree with the dependencies - that does drastically reduce the amount of dependencies required from the client code. –  Oak Feb 16 '11 at 11:57
1  
@Oak: Doing anything blindly is never good. One needs to look at pros and cons and make decisions based on evidence. This includes the Law of Demeter, as well. –  CesarGon Feb 17 '11 at 7:05
add comment

I've wrestled with this problem myself. The downside with "reaching" deep into different objects is that when you do refactoring you'll end up having to change an awful lot of code since there are so many dependencies. Also, your code becomes a bit bloated and harder to read.

On the other hand, having classes that simply "pass along" methods also means an overhead of having to declare a multiple of methods in more than one place.

A solution that mitigates this and is appropriate in some instances is having a factory class that builds a facade object of sorts by copying data/objects from the approriate classes. That way you can code against your facade object and when you refactor you simply change the logic of the factory.

share|improve this answer
add comment

I think the question is, whether you're using a suitable abstraction.

In the first case, we have

interface IHasGetA {
    IHasGetB getA();
}

interface IHasGetB {
    IHasGetC getB();
}

interface IHasGetC {
    ITransmogrifyable getC();
}

interface ITransmogrifyable {
    void transmogrify(x,y);
}

Where main is of type IHasGetA. The question is: Is that abstraction suitable. The answer is not trivial. And in this case it looks a bit off, but it's a theoretical example anyhow. But to construct a different example:

main.getA(v).getB(w).getC(x).transmogrify(y, z);

Is often better than

main.superTransmogrify(v, w, x, y, z);

Because in the latter example both this and main depend on the types of v, w, x, y and z. Also, the code doesn't really look much better, if every method declaration has half a dozen arguments.

A service locator actually requires the first approach. You don't want to access the instance it creates through the service locator.

So "reaching through" through an object can create a lot of dependency, the more so if it is based on properties of actual classes.
However creating an abstraction, that is all about providing an object is a whole different thing.

For example, you could have:

class Main implements IHasGetA, IHasGetA, IHasGetA, ITransmogrifyable {
    IHasGetB getA() { return this; }
    IHasGetC getB() { return this; }
    ITransmogrifyable getC() { return this; }
    void transmogrify(x,y) {
        return x + y;//yeah!
    }
}

Where main is an instance of Main. If the class knowing main reduces the dependency to IHasGetA instead of Main, you will find the coupling to actually be quite low. The calling code doesn't even know it is actually calling the last method on the original object, which actually illustrates the degree of decoupling.
You reach along a path of concise and orthogonal abstractions, rather than deep into the internals of an implementations.

share|improve this answer
    
Very interesting point about the large increase in the number of parameters. –  Oak Feb 16 '11 at 21:18
add comment

I often find that the logic of a program is easier to grok with chained methods. To me, customer.getLastInvoice().itemCount() fits into my brain better than customer.countLastInvoiceItems().

Whether that's worth the maintenance headache of having the extra coupling is up to you. (I also like small functions in small classes, so I tend to chain. I'm not saying it's right -- it's just what I do.)

share|improve this answer
    
that should be IMO customer.NrLastInvoices or customer.LastInvoice.NrItems. That chain isn't too long so it's probably not worth flattening if the amount of combinations are somewhat large –  konrad Feb 16 '11 at 20:58
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.