First off, I can't claim a lot of experience in OO design. Aside from what I learned in CS (a lot of theory :) I've come to .net from a VB6 background. Coming from that background, I had an expectation of what inheritance would be good for. That expectation was that I could extend a component/class to add a method/property that I felt was missing/needed. In the dark ages of VB6, I would use delegation with a private instance of the class I was extending, and write wrappers for every method/property of the private class. This was a real pain!
>From some great resources such as this list, I have come to see that is not what inheritance was made for (see the extending String thread on the DOTNET list). Judging by a lot of inheritance related questions, I see that I am not the only one that thought it could be used this way. But, the need to add methods/properties still seems to be legitimate! Ian's comment is that if a function can be done using the public methods of an object, then the function should be in a static utility class. This could work for the darken method mentioned in this thread, but what about something like adding a new property to the class? Why introduce a new class just to add a feature that will only be used by the one class? Aside from the additional namespace and documentation overhead (behavior defined in multiple locations), it's just ugly (IMHO). In general, is extending a class with additional (semi or fully orthogonal) functionality a bad OO idea, or is it the use of inheritance to extend the class that causes problems? Thanks, Erick ----- Original Message ----- From: "Ian Griffiths" <[EMAIL PROTECTED]> To: <[EMAIL PROTECTED]> Sent: Wednesday, September 04, 2002 11:05 AM Subject: Re: [ADVANCED-DOTNET] sealed: why? > > > So, are you in the "no, not all methods should be virtual" > > > camp? If so, can't you see that having classes unsealed > > > by default is equally dangerous. > > > > No I'm not in the 'all methods should be virtual' camp. But > > I think the issues are rather different for methods. Firstly, > > there is the performance question and the fact that you > > cannot inline virtual methods. > > Actually you can. Try saying that you can't on DevelopMentor's > ADVANCED-JAVA list (on the same LISTSERV as this), and see how long it is > before Jeff Kesselman jumps on you. > > JVMs have been doing what's called monomorphic inlining of virtual methods > for some time now. This is a technique where the JVM detects that you are > not exploiting polymorphism, and behaves as though methods weren't virtual. > (It also adds checks to make sure that if you do start exploiting > polymorphism it will re-JIT the relevant code.) > > Of course methods are virtual by default in Java, so there was more > incentive to develop technology that could inline virtual methods. But it > does illustrate that there aren't necessarily any perf advantages to > non-virtual functions. > > > > Secondly, if you make all methods virtual, then you > > lose the ability to make subtle decisions > > about how the class should be inherited. eg. if you have > > a Shape class designed for people to inherit from so > > that they can draw different single-color shapes, you'd probably > > choose to make the Draw() method virtual, but not the > > Color property. > > But doesn't the exact same argument you've been using against sealed also > apply here? Why prevent people from doing what they might think they want > to do in inheritance scenarios? (I think it's a specious argument in this > case, but I think it is in the class case too.) > > > > > Of course you'll say that in that example I've taken > > the trouble to design the class for overriding. If > > I've understood you correctly you want a class to be > > sealed unless you've specifically taken the trouble to > > design it for inheritance. > > That's about the size of it. It's just a safe-by-default thing. > Inheritance is definitely in the category of things you should only do if > you really mean it. > > Although as I said, this idea of making 'sealed' the default is not actually > what I think, it's just that I've found myself moving more towards that > opinion I think there are pros and cons to having classes sealed by default > and unsealed by default. > > > > > I think my reply would be that even if you haven't spent > > any time thinking about inheritance, then at least other > > people should by default have the chance to inspect your class > > and decide, given its public and protected methods whether > > they can usefully inherit from it. > > Again, surely you could apply that argument to virtual functions, where it > is manifestly wrong. If you haven't given any thought to how someone should > go about overriding a function, then the chances are they're not going to be > able to do it - they'll have no idea what the right semantics are: should > they call the base class, and if so before, during or after their own call, > and what invariants is the method supposed to uphold? And those are just > the straightforward ones - if you start getting into things like lock > semantics in a multithreaded environment it doesn't even bear thinking > about. > > > > > You might not think they can do much with it but > > other developers will _always_ > > come up with ideas you haven't thought of. > > Which is precisely what troubles me. The general rule with code is that if > you don't test it, it probably doesn't work. So if someone derives from my > class to use it in a way I haven't thought of, I won't have constructed a > unit test for that scenario, so there's a considerably better than evens > chance of it not working. > > Unless they're bolting on entirely orthogonal functionality. In which case > that sounds like a design error, and, more importantly, nothing they > couldn't have done without inheritance. Inheritance is not appropriate for > utility functions, or for composition of code. > > > > > I think I'd find the arguments that you and Mike are > > putting for the dangers of inheritance a lot more > > convincing if you could come up with a concrete > > example of how you can break a class by deriving > > from it > > That's a slight mischaracterisation of my argument - it's not breaking of > the class itself that really worries me. But this might be close enough to > keep you happy - it's an example of something else breaking because of a > class being derived from: > > public class MyFoo { > private int spongVal; > public int Spong { get { return spongVal; } set { spongVal = value; } } > > public override string ToString() { > return spongVal.ToString(); > } > } > > along comes a well-meaning developer who derives from this thus: > > public class MySubvertedFoo { > private string nameVal; > public string Name { get { return nameVal; } set { nameVal = value; } } > > public override string ToString() { > return string.Format("{0}, '{1}'", base.ToString(), nameVal); > } > } > > > So far so good - doesn't look like MySubvertedFoo has broken anything - just > added a new property, and has modified ToString. But what's this? Back in > the first assembly we find this: > > > public MyControl : Control { // Oo look! Inheritance, only good ;-) > > [TypeConverter(MyFooConverter)] > public MyFoo Foo { get ... set ... } > ... > } > > public MyFooConverter : TypeConverter { > public override bool CanConvertFrom(ITypeDescriptorContext c, Type t) { > if (t == typeof(string)) return true; > return base.CanConvertFrom(c, t); > } > > public override object ConvertFrom(ITypeDescriptorContext c, > CultureInfo ci, object o) { > if (o is string) { > string s = (string) o; > int spong = int.Parse(s); > MyFoo f = new MyFoo(); > f.Spong = spong; > return f; > } > else > return base.ConvertFrom (c, ci, o); > } > } > > > So, what happens if somebody innocently does this: > > myControl1.Foo = GetAMyFoo(); > > That will compile. And it's not particularly unreasonable. But what if > GetAMyFoo() actually creates a MySubvertedFoo? > > Well it might work. But if someone manages to use it in the Visual Studio > .NET Forms Designer, (which would probably require the line of code above to > be in the constructor of a form from which someone else derives, in order to > get that line of code to execute in the designer) they would get a > FormatException if they tried to edit the Foo property of the control. (And > the same would happen if they were using the PropertyGrid outside of VS.NET. > Or any other piece of code which relies on the TypeConverter > infrastructure.) > > So, we didn't break the class. We broke something else that was relying on > non-obvious semantics of the class. > > > OK, so it's a bit obscure. But then that's the point. And it's not > unrealistic. We hit problems because of unanticipated and totally > non-obvious semantic constraints. TypeConverters are ubiquitous and yet > most people don't know they exist, so it's pretty easy to make this > particular mistake. > > How can you ever be confident that you're safe from all such presumptions > that other code makes about the class? > > This is one of the biggest problems with inheritance. It is virtually > impossible to document fully the set of constraints on an object's > behaviour. (The exact same problem surfaces in scenarios other than > inheritance by the way. This is why it's so easy for a component upgrade > that ostensibly only contains bug fixes to break code that works just fine > with the older, supposedly buggier component.) > > > > > As far as I can see, if you haven't thought about how a > > class would be used for inheritance then you > > won't have defined any protected members will you :) > > You'll have made all the members public or private. > > Derived classes won't be able to access private > > members. And you've presumably designed the public > > ones so they are safe to use. How can a derived class > > change that? > > You've overlooked overrides of functions that were virtual in the base class > of the class you intend to derive from. Of course you could respond to that > by saying that override should default to sealed, and that you should > explicitly mark methods as virtual if you want derived classes to be able to > override them again... (I think that would be reasonable. I've never > really thought about this particular aspect before, but I don't think I like > C#'s defaults here. And it's arguably inconsistent - interface members use > virtual dispatch but are sealed by default...) > > So supposing we run with a new policy - override *all* virtual methods in > the base class, and mark them as sealed. That would eliminate the problem I > just showed. So now the question is: what use is inheritance? What can you > do with it that you couldn't do without inheritance? > > Can you show me an example of where it is useful to derive from a class in > the .NET framework that doesn't override any methods and doesn't use an > protected members? > > For the record, I don't like the example you gave here: > > > Even if you have a class with no virtual methods and > > nothing that looks useful for inheritance, other people > > might still wish to derive from it just for the syntactical > > convenience of having a class that implements whatever > > your class does plus some other features. One example > > of this is System.Drawing.Color. You can't derive from > > that because it's a valuetype - but if you could derive > > from it I would have done [1]. I'd love to have the > > syntactical convenience of having a color type with > > some extra methods like Darken(float fractionToDarkenBy) > > or some operators to add two colors together (add > > RGB components). **Such methods are completely > > orthogonal to existing methods** > > (my emphasis) > > So what are you doing using inheritance then? Such methods have no business > being in the Color class if they are completely orthogonal to everything it > does. (The ControlPaint class is the right place for, and where you will > find, the nearest equivalent to your Darken method.) The consensus in the > C++ community seems long since to be that such methods should actually be > global methods. (Global functions seem to have had a bit of a renaissance > in C++.) > > I think this is classic example of what Craig was talking about when he said > that inheritance is like a mirage - from a distance it looks like what you > want, but turns out to be a disappointing pile of sand close up. You have > presented an attractive syntactical shortcut, which makes it look like > inheritance would be useful here. But with a little thought I think it > becomes clear that inheritance is The Wrong Thing here. > > There's a very good reason why the two methods you describe should not be > members of a derived class (beyond the minor technical nit that they can't). > I could very reasonably want to use those on any Color. Why should I be > required to obtain your special ColorWithUtils just to use them? What if > someone else does the same thing with their own MagicColor class - I now > have two mutually incompatible versions of the Color class, and I have to > choose which one to use based on an arbitrary split across some utility > functions, the split being based on nothing more than who happened to decide > to write which utility functions. > > Such methods clearly belong outside of the class. The usual heuristic that > the C++ community (or those C++ mailing lists I still subscribe to) uses is > that if a method is capable of doing its job by acting just on the public > members of a class, then it should be a global function. It absolutely > should not be a member of the class on which it acts. > > Of course we don't have globals in C#, so we end up with statics instead, > which look an awful lot like globals in a namespace... So you end up with > methods like ControlPaint.Dark(myColor, myPercentage) which do exactly what > your darken method does, but can do it to *any* colour, not just your > special derivative. > > > Your argument is essentially one of syntactic convenience. I'll buy that > syntactic convenience is potentially a good thing, I just don't agree that > inheritance is the right way to go about it. It seems too high a price to > pay. And to be honest I've never found myself in a position where I've felt > even remotely tempted to use this trick. (So I don't agree that this > particular syntactic convenience would be all that useful. Exactly how much > typing is this really going to save you?) > > > > It's examples like that that you'd kill by routinely > > declaring classes as sealed > > Good. :-) > > > when there isn't a good specific reason to. > > Sounds like reason enough to me... You've convinced me - I'm moving over to > sealed as my new default. Together we can stop this madness. ;-) > > > -- > Ian Griffiths > DevelopMentor > > You can read messages from the Advanced DOTNET archive, unsubscribe from Advanced DOTNET, or > subscribe to other DevelopMentor lists at http://discuss.develop.com. > You can read messages from the Advanced DOTNET archive, unsubscribe from Advanced DOTNET, or subscribe to other DevelopMentor lists at http://discuss.develop.com.