Interesting conversation going on over on CodeFez, where Nick Hodges contends that MS doesn't quite get OOP, and Julian Bucknall responds that it's actually Nick that doesn't quite get it.
Let me first say that there are few, perhaps zero, OOP frameworks in the world that are both commercially successful ("commercial" in the sense of universally used, not necessarily in the sense of revenue generating) and lacking in design and implementation warts. Nick focuses in on FCL, which certainly has a few warts (such as seemingly excessive implementation hiding), although it is by and large an excellent OOP framework. Nick seems to favor VCL, which is also a very good framework, but it does tends to suffer from an overly monolithic inheritance model (can lead to unnecessary binary bloat) and tight class couplings (which can present difficulties for extensibility). Nick correctly points out that MFC is hardly what one would call "OOPish" but more of a collection of handy objects to wrap API primitives and common application patterns. I think ATL is a good example of a successful and still very OOPish framework, although it is comparatively smaller in scope and admittedly rife with "C++ OOPisms" like templates and multiple inheritance that don't always sit will with non-C++ folk. And you certainly won't hear me making arguments that VB6 is object oriented (object-based, perhaps), so Nick and I are in agreement there. There are of course many other frameworks out there, but I don't want to stray too far from the point by going encyclopedallistic.
I'd like to talk specifically about Nick's comments, but before I do that I'll give you the abbreviated Reader's Digest version of Julian's response: where possible, use composition patterns instead of inheritence patterns.
Nick supports his thesis that MS doesn't quite get OOP with a few anecdotes from his experience with the framework. It seems to me, however, that his critiques are more about Nick not liking some bits of FCL as opposed to these anecdotes serving as evidence that MS doesn't quite get OOP. Let's look at these anecdotes point by point...
Point 1: "For instance, why is there a separate Connection class for each database type in ADO.NET? OracleConnection, SQLConnection, OLEDBConnection – one for each database! And you can only connect a SQLDataAdapter to a SQLConnection. "
This is really two issues. The answer to the former issue is that the db connection classes follow a composition pattern, not an inheritence one. SQLConnection, OracleConnection, and OleDbConnection all implement IDBConnection, which is an interface that surfaces commonalities among db connections. To the latter issue, SQLDataAdapter (by way of its constituent SQLCommands) connects only to a SQLConnection because SQLDataAdapter is designed to be used only with SQL Server databases. One could perhaps question SQL Server being "special cased" here, but that isn't an OOP design issue it's a product functionality decision. From a design standpoint, one should note that the data adapter is also abstracted by the DbDataAdapter class which implements the IDBDataAdapter and IDataAdapter interfaces.
Nick also goes on to point out, correctly I think, that BDP is better at insulating the developer from different database vendors, but - again - this is a product functionality decision, not a fundamental design problem.
Point 2: "The Style class allows you to set properties -- Bold, Underline, Font, etc. -- and have those values rendered as part of an ASP.NET control. Well, I was building a control that needed a very specific type of Style, but the problem I quickly ran into was that I didn't want all of the properties of the Style class to be part of my new Style..."
This seems to be a relatively minor point, but I can see Nick's issue with controls that only want to use a portion of the Style class. However, I'm not sure Nick's proposed solution of reducing style elements to strings that must be manually parsed and type checked is a better approach. It doesn't seem that big a deal to me to create a MyStyle class that only contains those elements you need. I'm also just thinking off the top of my head about this, so I'm not saying my idea is necessarily better, and the point about retrograde extensibility is well taken, but the Style class certainly doesn't jump out at me as an example of bad OOP design.
Point 3: "How about this – try reading in a text file, altering the third line of text in the file, and then writing it back out again."
Okay, now we're getting into framework functionality, not OOP design. However, I'll bite. Here's one way to do it using the new C++/CLI syntax from VC++ 2005:
System::IO::StreamReader sr("c:\\temp\\foo.txt");
cli::array<String^>^ slist = sr.ReadToEnd()->Split(L'\n', true);
sr.Close();
slist[2] = slist[2]->ToUpper(); // convert 3rd line to upper case
System::IO::StreamWriter wr("c:\\temp\\foo.txt");
for each(String^ s in slist) {
wr.WriteLine(s);
}
You might be able to do this a little more briefly in other languages but not by much. It's also worth mentioning that C++/CLI syntax allows for stack semantics on CLR objects (sr and wr in the code above). As you would expect in C++, objects instantiated this way are automatically disposed (or, in .NET speak, IDisposable::Dispose is called automatically) when they go out of scope. In other words, I don't need try/finally blocks or using statements, which saves me some lines of code versus most other .NET languages. :)
Point 4: More ADO.NET complaints: Why is it so tough to get a data value out of a table? I have to write:
CustomerID := Convert.ToInt32(MyDataset.Tables[0].Rows[0]['CUSTID']);
when the above code is crying out to be
CustomerID := MyDataset.Tables[0].Rows[0]['CUSTID'].AsInteger;
Okay, so the first one returns an System.Object that you have to explicitly convert and the second returns a TField object that has conversion methods hanging off of it. I admit the TField is handy and maybe even nicer from a usability standpoint, but I have trouble seeing this as a huge issue or an indication that the implementer doesn't quite get OOP.
Point 5: And while I'm at it, surely I am not the only one that finds the complete lack of the concept of a current record in ADO.NET a glaring omission
I admit that I'm kind of spoiled by the local DB syntax as well. Although you can use an XxxDataReader to iterate over a result set record-by-record.
I might add that I'm also pretty much in agreement regarding sealed classes. If a framework were a party, sealed classes would be that immense guy with no neck standing in front of the velvet rope, denying access to the fun within. It's true that sealed classes have potential for better optimizations; the compiler may, for example, be able to make a direct call to a virtual function rather than going through a vtable indirection since there are guaranteed to be no derived classes, but the benefit of such an optimization is pretty thin to warrant the cost in extensibility. While it's legitimate from an OOP standpoint to seal a class that is not intended to be derived from, this seems almost egotistical to me on the part of the implementer, as if to say, "nobody could possibly think of a way to use this class that I have not envisioned." I can, however, see a few very legitimate reasons to seal a class: the first is if the compiler has carnal knowledge of the class and uses some form of compiler magic to make the class have useful or much more efficient behavior, then it might be worthwhile. Second, in some cases it's reasonable to seal a class to limit vulnerability surface area for security reasons. Finally, while we would all like to have the time to write perfect software, it's valid to seal a class to reduce its scope of capabilities in order to lessen development, test, doc, etc. impact in order to meet a budget or schedule. In such cases it can always be "unsealed" later if there is time and demand to do so.
In summary, I think there is very little evidence in Nick's anecdotes that points to some fundamental misunderstanding of OOP. I think quite the contrary, FCL makes good use in particular of composition patterns by abstracting with interfaces. It has its issues and crufty corners, but what framework doesn't? Let's also not forget that SDKs and frameworks, like all software, are developed on a schedule. So, I'm sure there are cases in FCL (and any other commercial framework) where a "pure" approach might have had to be ditched for a more expedient one as one deadline or another loomed.