Holy Wars
Sean apparently doesn't like inheritance or getters / setters.
“...you should be implementing interfaces instead of extending classes.”
“...The bottom line: slavishly providing
getX()
/setX()
methods for all your instance data will buy you little but complexity!”
I agree that inheritance, if not used properly, can get you in a lot of sticky situations; however, I have found that the solution many times has less to do with interfaces vs. inheritance and more to do with composition vs. inheritance. Base classes can be extremely useful because functionality and/or fixes can be added to the base class without breaking all the existing implementations (of course, composition can do the same thing to some extent, because you can modify one class without changing all the classes it uses). Interfaces are best used for systems where the the potential for the entire functionality of the component to need to be swapped out is pretty high (IComparer is a good example of this). However, if you have something like XmlNode, most XmlNodes contain a lot of common functionality, so inheriting that base functionality is a good way to reuse your code. Still, there isn't any rule that says you can't do both. CollectionBase is a very good example of this. CollectionBase is an abstract class that saves you a lot of coding when you are creating your own custom collections (which happens quite a bit in .NET land since we don't have generics yet). As useful as this class is, however, the designers of the framework realized that not all collections are simple lists, so you also have the option of implementing the ICollection interface instead. Yes, it will take a bit more code, but you can do a lot more custom coding. One place that this would be very useful when creating an optimized sorted list.
As far as getters/setters are concerned, I don't agree with the article Sean points too on a number of counts:
“This implementation hiding principle leads to a good acid test of an OO system's quality: Can you make massive changes to a class definition—even throw out the whole thing and replace it with a completely different implementation—without impacting any of the code that uses that class's objects? This sort of modularization is the central premise of object orientation and makes maintenance much easier. Without implementation hiding, there's little point in using other OO features...Getter and setter methods (also known as accessors) are dangerous for the same reason that
public
fields are dangerous: They provide external access to implementation details. What if you need to change the accessed field's type? You also have to change the accessor's return type. You use this return value in numerous places, so you must also change all of that code. I want to limit the effects of a change to a single class definition. I don't want them to ripple out into the entire program. ”
First off, a system's quality has nothing to do with whether or not you can rip out any given piece and replace it with an entirely new implementation. A quality system is flexible where it needs to be, not flexible for flexibility's sake. There are not enough hours in the day to design such systems, and even if there was enough time in the day, most of it would be wasted creating sophisticated extension mechanisms that will never be used. COM tried to do this sort of thing and it ended up so complicated that even the guys at MS could barely get it to work (ask Don Box about that one). In any case, since when does implementation change the data types that are used? Data types are not implementation details, they are part of the contract of your class. I've been programming for many years now and it is extremely rare that I run into a situation where I need to change the data type (even in such a small way as from “int” to “long” as the article points to as an example). However, I do many times run into situations where I want to change the persistance mechanism for a variable or want to fire a notification event to update some other variable somewhere. These are implementation details. For example, do I want to store this data in session, viewstate, or the request context? (yes, you actually run into situations where all three options will do the trick and you need to make a choice). Regardless of which one of these persistence mechanisms I choose, classes which access my property will function exactly the same. Why? Because it is an implementation detail, not part of the class's contract.
Now, what about minimizing the data flow through your application? Generally, this is probably a decent piece of advice for the data tier, and possibly the business logic tier. In fact, a lot of times things such as latency over remoting will dictate that you drop your get/set methods. There is definately a lot to say for message oriented architectures, which take this concept and run with it, but it is a shame that the author didn't bring this out a bit. He hints at “the message” a few times, but never explains that this is where his concept of minimizing data flow is really best put to work.
Then, there is always the issue of the UI layer or Components, which even the author points out is a problem. His solution, put UI code in the business logic layer--which he rationalizes by saying that AWT and Swing are abstractions, not UI code--or use private fields and reflection to set the values. Now, if that itself doesn't make you question the author's judgement at least a little bit, then you are probably just as much off your rocker (or perhaps, you never got on it to begin with). The issue is more than “just a little clutter”. What happens if your UI isn't even in the same assembly as the business logic (ie., you are using webservices to create a RIA). You can't just solve the problem by putting this logic in an inner class or using a facade, because in that case you simply can't make any calls to the client to draw your UI. Go ahead, handycap all your applications and go back to the 60's. Then, when management gets all hyped up about the new Flash RIA tool they read about in PC Magazine and tells you that you need to create a slick interface for all the execs, have fun rewriting your code to make it work the way it should have in the first place.
In any case, both articles are prime examples of what happens when someone takes something too far. There is a bit to be said about avoiding complex class inheritance chains and minimizing data flow, but taking it too far is just as bad (if not worse) than not doing it at all. The key here is balance, examine your requirements, take a step back, and think before you code.