Short answer: The problem is that the caller needs to traverse (and know the structure of) the entire multi-object data structure in order to access it's leaf node. I think you're confusing things a bit. You're arguing: if A knows about B and that's ok, and B knows about C and that's ok (etc.), then it must be ok for A to know about C. But it's not transitive like that, because the issue isn't that A knows B and B knows C, it's that the caller knows A, B, C, and D and how they all connect to each other. That's a lot that the caller has to know in order to get to D.
Long answer:
- Chaining in this case is bad because:
- It makes for brittle code. If you change the interface to floor, wall, or door, then the calling code can't access doorknob the way that it used to. So imagine you have a few parts of your code that access doorknobs, and a few that access doors, and a few that access walls, and then for some reason you have to change the floor class. Now you have to change all the code that accesses any of those objects.
- How will you test this? If you want to use object doubles, you will have to make
getFloor(0)
return an object double that responds to .getWall(WEST)
and returns an object double that responds to .getDoor()
and returns the double of the doorknob object that you want to use in your white box testing. Also point one applies to all your tests also.
- Change happens. (this point especially relates to the TLDR below.) What if my house wall doesn't have a door - it's a patio floor? Do all floors have walls, all walls have doors, and all doors have doorknobs? We're assuming a lot about how each of these works.
- I'm going to answer this with another question. In a long while when I am a grandfather, if I know where my son's daughter's friend keeps her bike, is that intimate knowledge? I'd have to know my granddaughter's friend pretty well... so yes, I would say it's relatively intimate knowledge. But for my granddaughter, that would be no big deal to know where her friend keeps her bike. Because I am further removed, having detailed information is more intimate than if I was not far removed. Also, see point 1.3 above.
- see (2)
- The issue here is that the details of the entire multi-class data structure have to be known in order to access anything in the data structure. Of course for one object to use another it needs to know all about it's public interface. It's when you scale that up and need to know that A returns a B which has function C that returns object D which has function E that returns object F that you are really describing an entire data structure every time you want to access one of its members).
- Two levels deep is about as far as I would be comfortable going, but I'm keen to hear if anyone has anything more substantial than a gut feel for this question. So I would be ok up to this point:
house.getFloor(0).getWall(WEST)
- The goal is to make all of your house parts less tightly coupled. You want a way to get a reference to the doorknob, given a reference to a house, without traversing the entire house structure.
- One possible solution is to store the house structure in a format that allows querying - as a relational database or in xml. Then you can
SELECT * FROM doorknob WHERE house = '45' AND floor = '1'
or house//floor//doorknob
(searching for any doorknob for a certain floor in the house, not caring that there are walls and doors between the floor and the doorknob). The point there is that you don't care about all the intermediates house parts.
- But I should really zoom out and say this: the point of this whole discussion is that if you
are().tempted().to().chain().functions()
as badly as that, then it's a code smell; an indicator of a more fundamental problem with your design. You need to redesign the data structure so that you can access parts without knowledge of the whole. Maybe you find yourself iterating over doorknobs a lot, and the house object needs a function that returns the list of doorknobs with some meta-data about their rough location.
Conclusion:
One general rule of thumb for architecture presented in Design Patterns: Elements of Reusable Object-Oriented Software (Erich Gamma et. al., Chapter 2) is to "encapsulate the concept that varies". In the case you're presenting, with all those different objects, something (I don't know what) is bound to vary (it depends on the problem domain; we don't have the background info to know what could change here, but the owner of the code in that example would know. I've highlighted a few things that I thought could change in point 1.3 above). So if somethings going to change, then something should be encapsulated. The design challenge is to know what could change and encapsulate that... but as it stands, the house structure is totally un-encapsulted. It's intimate details are publicly known.