I was having an IM chat with an old friend yesterday afternoon (let's call him A). He's starting to pull his hair out (he has more than me, so I'm not worried yet) with regard to the obtuseness of his fellow developers. They're somewhat prone, shall we say, to producing unwieldy class models that can't easily be extended or maintained.
We chatted a bit about implementation inheritance versus interface inheritance using composition. Implementation inheritance is what programmers normally think about when they hear "inheritance". They think of a class hierarchy, they consider which methods to make virtual (or, if you're using Java, which methods if any to make final), they worry about private versus protected. Interface inheritance is when you write a cless that implements one or more interfaces, usually through some kind of composition technique.
J: The big problem with implementation inheritance is that you are tightly coupling the descendant to the ancestor. The ancestor is not this black box, it's much more transparent than that.
A: Right, exactly.
J: As soon as you have a virtual method you impose this awful problem on the writer of the override. Do I call the ancestor's method? If so, when can I call it? At the beginning of my overridden method? At the end? Anywhere I like? Can I set up some data, call some other ancestor methods first? Or is there some calling dependency I don't understand here? The writer of the descendant is forced to read and understand the code for the ancestor.
A. And it's not just the virtual methods, it's the ancestor's local data. Should the ancestor make this data available directly as a protected field? If so what are the ramifications of this?
A. So I'm thinking about it, and I'm mostly coming up with the conclusion that some form of composition results in more flexbility and eaiser to understand code when compared with traditional implementation inheritence. I'm thinking that maybe I should talk about this to the developers at one of the design meetings.
J: The problem is, will they understand?
A: The problem is, the ones who need to understand never come to the meetings :(
J: Aye, true.
J: You really need a good refactoring tool though.
J: It's not impossible without, just more difficult and a bit tedious.
A: Do you have any articles you recommend on composition over inheritance?
J: Let me check some out. The Design Patterns book by the GOF is a paean to composition over inheritance.
J: The first thing to drill into their heads is Single Responsibility Principle (SRP), but some really don't get even that.
A: Good point, maybe I should be focusing on the principles and see what I get from that...
J: SRP is the biggest because it's the hardest to get across.
A: I think the problem stems from how programming is taught.
J: Yep. Or, people come to OOP through procedural (say to C# from VB6) and program in the same way they always did.
A: It's taught backwards -- you're told to think of the data, so all your classes become nothing more than structs, and then you think of what you do to that data, and you never quite get to how the system interacts, and it's those relationships that make the code work or not.
A: We should start with discussing interactions and relationships of classes, not data packaging
J: Yep. Teach behavior, not data. The bible of that is "Object Thinking" by David West. I worry it's above most people's heads.
A: It will remain there until they are paired with people who can teach it, or find a job where it doesn't really matter :(
J: I'd say, start with SRP. Take some class and show how it has more than two responsibilities and break it apart. Say, any class in [A's company's main application]. LOL
A: "now see this class has 36 responsibilities, so we type "del class.cs" and start over"
J: Trouble is, SRP requires thinking about design and understanding choices and knowing why A is better than B.
A: Judgement... hard to teach.