sourmìlk wrote:I'm not sure I get why how TableRowCollection works matters here. Isn't the point that it's a member of Table that I can access without restriction enough to say that it violates principles of data hiding?

It's not! Or at least, as I demonstrated, doesn't need to be to meet the documented interface (even though, to be fair, it probably is implemented that way).
Data hiding isn't just some buzzword you want because it sounds fancy. The reason you want it is because it will let you change out the implementation (in this case, of Table) without affecting the interface. Table's interface doesn't really violate that because new implementations can pick alternative implementation strategies (which is why my example and how TableRowCollection works is relevant, because it proves it).
I'm not saying that it's completely without its problems; in particular, it would be somewhat easy to write code that assumes that
some_table.Rows == some_table.Rows is always true, in which case my implementation above will break that assumption, but even that can be worked around.
And the design has benefits. For instance,
TableRowCollection implements
IList, which means you can pass
some_table.Rows to some function that wants an
IList. It is a completely reasonable (and I'd argue
good) tactic to not make
Table itself implement that interface. In particular, there are at least three properties of
Table that return a collection, and at least one more which would be pretty natural to add (columns). Which one is it going to use by default? Now, you could argue that
.Rows is pretty natural to do by default, but "pretty natural" doesn't necessarily mean "the best choice". Personally, I'd
rather see the proxy object.
Compare to two things in other programming languages. First is Python's dictionaries. There are at least three ways to iterate: over keys, over values, and over key,value pairs. Python's designers decided that by default, dictionaries should iterate over keys, but in my opinion it would be equally reasonable to decide that dictionaries should iterate over key,value pairs (after all, that's what's actually in the collection) -- and thus in my opinion, it would also be equally reasonable to decide that dictionaries shouldn't be iterable at all without being explicit about what you want. Now this analogy isn't very close, so let's go with something that's actually very good: Boost's
bimap, which is like a bidirectional version of
std::map. (I'll say it stores pairs made up of a left and a right. You can then look up a right value given a left one, or vice versa.) One way to implement this interface would be to make a class with
left_to_right() and
right_to_left() functions. And then you have a weird class that's sitting out there with its own API. But that's not what Boost did. Instead, a
bimap object
b has fields which you can access via
b.left and
b.right. You then say something like
b.left[l] = r and then
b.right[r] == l. And
b.left and
b.right are interface compatible with
stl::map, which means that you can pass them to template functions which expect a map. That's
good design, not bad! And it doesn't break information hiding: it tells me almost nothing about how
bimap works internally. The only information I have about how
bimap actually works is what I can infer from general data structure knowledge.
Ben-oni wrote:Table (there are a few different ones we could be talking about)
system.web.ui.webcontrols.table is what's under discussion, though I don't think that's been mentioned since the start of this topic.
Also, "Table, as a Control of some sort (depending on which namespace we're talking about), has information about rendering and interaction, and cares nothing of the data model. Encapsulation requires that the data implementation be kept separate from that, i.e., by putting it into a property." is a really good way of putting it.