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
, 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()
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
has fields which you can access via b.left
. You then say something like b.left[l] = r
and then b.right[r] == l
. And b.left
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.
(there are a few different ones we could be talking about)
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.