Steve Teixeira, now a supplicant, er, sorry, employee for Microsoft — yes, sadly, it is true, he’s shaken the Delphi dust off his boots and drunk deeply from the MS Kool-Aid Stand — responded to my recent CodeFez article about MS not quite getting it in the area of OOPishness.
The first thing I want to say to Steve is "Hey, thanks!" It’s been my goal for months now to get a reference from a Microsoft blog to one of my CodeFez articles. Mission accomplished!
Now that I’ve been nice and appreciative to Steve for fulfilling a long unrealized dream, I’ll proceed to rip his blog post to shreds like a puppy with a Sports Illustrated rubbed in bacon.
Steve discusses five points from my article. I’ll respond to his comments about those five points, destroying each in turn. It’ll be like Perry Mason vs. That District Attorney Guy who never won a case. (By the way, That District Attorney dude was named Hamilton Burger. Wouldn’t you love to have a friend named Ham Burger? The laughs would just keep on rollin’, hanging around in the bar after Ham lost every case to Mason. Good times.)
Point 1: Steve "refutes" my complaint about the poor design of the myriad of Connection objects in ADO.NET by saying "that isn’t an OOP design issue it’s a product functionality decision." I must say, my initial response to that was "Huh…? What does ‘product functionality decision’ mean?" How is this not an OOP design issue? Instead of designing a single class to do the job, they design multiple classes that can’t replace one another. In addition, each class requires the use of a database-specific implementation of the IDBDataAdapter which can’t be interchanged either. And don’t even get me started on things like OracleDateTime and OracleParameter. You can’t even call that stuff "somewhat abstracted and decoupled". To his credit, Steve concedes that point when he says "Nick also goes on to point out, correctly I think, that BDP is better at insulating the developer from different database vendors." Well, yeah, exactly! That’s what the Borland Data Provider does: it uses good OOP technique to encapsulate and abstract ADO.NET so that you don’t have to hard code things like OracleString into your code. That’s what good OOP design is supposed to do. Steve says it’s not a fundamental design problem, but I say a total lack of abstraction to interfaces and a tight coupling of a specific implementation to the interfaces is bad design. The BDP doesn’t do this. That’s good design.
Point 2: Steve misses the boat altogether when talking about the Style class. Sure, you can create your own Style class and implement it in your ASP.NET controls. But to do that, you have to completely abandon the built-in functionality that the framework supplies for dealing with Styles, and do everything "by hand". The class System.Web.UI.WebControls.Style has a property called ControlStyle which is of type Style. If you want your control to have a style that doesn’t descend from the Style class — and thus include stuff that you might not want — you are out of luck. You can’t partake of the WebControl class’s style handling. You have to do it all yourself. This is a perfect example of MS not getting it. They’ve provided a base style for you that you must use no matter what — even if you don’t want some of that functionality in the Style class. Again — bad design. It’s poorly designed because it makes assumptions about descendent controls that shouldn’t be made.
I’ll have to agree with Steve when he laments my example for defining an IStyle. I did say that I was "designing on the fly." I didn’t propose my example as the perfect solution, but merely as an example of how it might be done: i.e. the ControlStyle property should have been an interface instead of a class which requires certain type to be used.
Point 3: Steve, Steve, Steve, Steve, Stevey, Steve-aroo, Steve-arama! Wow, you are playing right into my hands, just like that poor cop schmuck in The Usual Suspects when Kevin Spacey played him like a concert piano. Steve argues "Okay, now we’re getting into framework functionality, not OOP design" Well, no, the lack of OOP design is exactly what I’m talking about here. How is "framework functionality not OOP design?" Loading, reading, and writing text files is a very basic and common functionality. One would think that there might be a class that encapsulates that functionality. For instance, the description of such a class that would be used to do what I discussed might go like this:
- Create an instance of the TextFileManager class by passing the filename to the constructor
- Alter the third line in the text file, as described in the original problem set, described in the previous articles.
- Save the Result.
Simple, clean, neat, orderly, and — dare I say it! — well designed.
Steve’s example drones on and on like this:
- Create an instance of something called a StreamReader (you use a StreamReader to handle text?), passing the filename to the constructor
- Allocate memory for an array of strings
- Read through to the end of the stream, and when you get done doing that, peruse over the result, chopping the string into array entries every time you run into a "return" character
- Close the StreamReader
- Alter the third item in the array
- Create an instance of something called a StreamWriter (you use a StreamWriter to handle text?), passing the proposed filename to the constructor.
- Iterate over each item in the array we created above and write each one out to the StreamWriter.
Imagine my mirth, my barely controlled giggles, when Steve follows this up with "You might be able to do this a little more briefly in other languages but not by much." Uh huh. First, this isn’t a question of language, but a question of OOP. I’ll leave it to the imagination of the reader to determine which of the two above examples is a better example of encapsulation. (Hint: One uses a single class, the other uses two classes and an array.)
Point 4: Steve then goes on to write about my "getting data out of a dataset" argument. He writes, "Okay, so the first one returns a 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."
Doesn’t quite get OOP? The designer here returns an instance of the root object. Very, very helpful. Not! The class itself could provide easy conversion to other types, but it doesn’t. Easy to do, obviously needed, but there’s nothing there. All you get is an instance of System.Object. And this says to us that the designer "gets OOP"? What it says to me is that the designer thought that it was quite OOPish to have you go off and call some other function from some other class to perform the basic functionality of retrieving data from a dataset. Thinking it OOpish to need to call another class to get the original class to perform basic functionality of the field is not my idea of "Thinking in OOP". As Steve says, the TField class is handy. It’s handy because its a sound implementation of OOP design to perform the task at hand! We can call the Convert class a class, and technically it is, but it’s really just a container for library functions. Needing another class to perform the basic functionality of your class — and yes, returning values is a basic functionality of a dataset — isn’t OOPish.
Point 5: My Point 5 here wasn’t really meant to be an indictment of the OOPishness (I love that word!) of ADO.NET, but just a general lament. Sure, you can iterate over a result set with a DataReader, if you are connected to the server! If not, you are pretty much stuck doing the foreach thing over the rows. The notion of a current record requires binding the data to the interface. Very uncool. The general point stands — the concept of a current record is a basic database concept, and totally devoid from ADO.NET.
I appreciate Steve’s general agreement about sealed classes. (I didn’t even talk about adding final to a method. Argh, how unfriendly can you get? "Hey, this method used to be virtual, but I’ve categorically decided that you can’t descend from it anymore! Neener, neener, neener!") I think it hard to argue that sealed classes are anything other than totally lame. I can even go so far as to grant Steve’s basic point that, lame though they are, a programmer should be able to seal a class. Such a programmer would be a big weenie to do so, but, hey, that’s the programmer’s decision. And of course, I can mock such a decision.
Steve does argue a few points in favor of sealed — that a class may need to be sealed to help the compiler. I counter that OOPishness knows not, and should not know, of compilers. If you are making OOP design decisions based on what a specific compiler needs, then you aren’t making good OOP design decisions. In fact, the FCL is supposed to be language neutral, so OOP decisions based on compilers isn’t supposed to even be a factor. He also argues that there might be security reasons for sealing a class. Well, maybe, but I can’t think of any right now. I’m happy to be educated on that point.
Steve summarizes: "In summary, I think there is very little evidence in Nick’s anecdotes that points to some fundamental misunderstanding of OOP." Weeeeeeeeeelllll, I beg to differ. I think every single one of my anecdotes speaks directly to the issue of a lack of good OOP decisions, as illustrated above in my stunning repartee to Steve. Each of my examples speaks about nothing but design decisions made by Microsoft designers that either limit your ability to implement a descendent class, couple tightly your code to a specific class, or force you to use a class that you don’t necessarily want to use. And they are, of course, merely a sampling of things that I could have talked about. For instance, try to descend from the StringCollection.
Hey, now that Steve is a Microsoftie, I expect him to defend the home field. But since he is a Microsoftie, I also expect him not to quite get it.