December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dan | On Thursday, 20 December 2012 at 14:51:31 UTC, Dan wrote: > On Thursday, 20 December 2012 at 07:48:12 UTC, foobar wrote: >> >> >> This is trivially implemented with a simple OO design pattern in any usual OO language: >> >> class Base { >> public override precious() {...} >> private void myPrecious() { >> before(); >> precious(); >> after(); >> } >> } >> >> class Derived { >> override precious() {...} >> } >> >> Having syntax sugar is redundant given how simple this pattern is. > > I think it is more than syntactic sugar and in this case it is trivial because you as a designer of Base anticipate only one level of Derived. But, assume there are to be 2, 3, 4, or more levels of derivation. In fact, maybe you don't know how many levels there will be. For a chain of B->D1->D2->D3 I don't see how you can up front get the same effect this way. At the level of D2, base will skip the implementation of precious() in D1. At the level of D3, base will skip the implementation of precious() in D1 and D2, etc. Chaining is lost beyond the first level. It is as if something is done in one direction, you want it done the other and you provide an example that does the reversion as long as there are only two entries. It is not truly a general solution. > You misunderstand. It is precisely what the quote bellow says - it saves you from coming up with new names. The same pattern can be applied multiple times for the multi-derivation case you bring up, it's just needs more method names. To extend my previous example: class Derived { override precious() { beforeDerived(); furtherPrecious(); afterDerived(); } } class FurtherDerived : Derived { void furtherPrecious() {...} } Now, we get this call chain: base.myPrecious() -> Derived.precious() -> FurtherDerived.furtherPrecious() This can be extended ad infinitum. > From the article: "If you chain more than two levels of subclasses, BETA scales better because you don’t need to keep coming up with new names." > > Thanks > Dan |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to foobar | On Thu, Dec 20, 2012 at 08:19:58PM +0100, foobar wrote: > On Thursday, 20 December 2012 at 10:15:47 UTC, Benjamin Thaut wrote: > >Am 20.12.2012 10:44, schrieb sclytrack: > >> > >>@mustcallbefore @mustcallafter > >> > >>Would these be automatically called in all derived versions? > >> > >>@autocallbefore @autocallafter > >> > > > > > >I think @mustcall would suffice, because it won't compile. If it does not compile the customer will investigate what broke and fix it (hopefully) appropriately. But of course the other options would be nice too. > > So basically you suggest adding a language feature to cover up library developer's poorly designed API? IMO, if you want to provide a frozen API under the current inheritance design, you should use pimpl design pattern in the first place and not ask for features to solve the problem after the fact. Yeah, it doesn't make sense for the base class implementor to say "you must call function X after you're done function Y". The base class API should be *designed* so that function X *will* be called after function Y, no matter what the derived class code does. OO code that breaks if methods are called in the wrong order (or certain methods fail to get called) is broken by definition. The object's internal state must remain consistent no matter what sequence its methods are called. This is a fundamental tenet of OO design. T -- Amateurs built the Ark; professionals built the Titanic. |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to foobar | On Thu, Dec 20, 2012 at 08:30:36PM +0100, foobar wrote: > On Thursday, 20 December 2012 at 14:51:31 UTC, Dan wrote: [...] > >I think it is more than syntactic sugar and in this case it is trivial because you as a designer of Base anticipate only one level of Derived. But, assume there are to be 2, 3, 4, or more levels of derivation. In fact, maybe you don't know how many levels there will be. For a chain of B->D1->D2->D3 I don't see how you can up front get the same effect this way. At the level of D2, base will skip the implementation of precious() in D1. At the level of D3, base will skip the implementation of precious() in D1 and D2, etc. Chaining is lost beyond the first level. It is as if something is done in one direction, you want it done the other and you provide an example that does the reversion as long as there are only two entries. It is not truly a general solution. > > > > You misunderstand. It is precisely what the quote bellow says - it saves you from coming up with new names. The same pattern can be applied multiple times for the multi-derivation case you bring up, it's just needs more method names. > > To extend my previous example: > > class Derived { > override precious() { > beforeDerived(); > furtherPrecious(); > afterDerived(); > } > } > > class FurtherDerived : Derived { > void furtherPrecious() {...} > } > > Now, we get this call chain: > base.myPrecious() -> Derived.precious() -> > FurtherDerived.furtherPrecious() > This can be extended ad infinitum. [...] But the fact that you keep having to invent new names is itself the problem. It inevitably leads to horrible APIs that consist of names suffixed by numbers: precious(), precious1(), precious2(), precious3(), ad nauseaum. Method names that are suffixed by numbers are, as a rule, an indication of bad API design. They also make your derived classes immovable around the hierarchy, because to override their parent's method, they have to use the exact name that's introduced by the parent class, otherwise they risk accidentally short-circuiting part of the chain. Whereas BETA's unification of the entire chain of calls under a single name means that it's trivial to shuffle derived classes around the hierarchy -- you're guaranteed they only overload their immediate parent's method, and it stems the inevitable tide towards the horrible number-suffixed method names. I'm not saying that the BETA approach cannot be represented by the traditional approach -- they are functionally equivalent, as you point out. It's just that for some use cases, the BETA approach is superior. (All high-level languages compile to ASM, but that doesn't mean they add no value to programming directly in ASM.) T -- If the comments and the code disagree, it's likely that *both* are wrong. -- Christopher |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On Thursday, 20 December 2012 at 19:46:25 UTC, H. S. Teoh wrote:
> On Thu, Dec 20, 2012 at 08:30:36PM +0100, foobar wrote:
>> On Thursday, 20 December 2012 at 14:51:31 UTC, Dan wrote:
> [...]
>> >I think it is more than syntactic sugar and in this case it is
>> >trivial because you as a designer of Base anticipate only one
>> >level of Derived. But, assume there are to be 2, 3, 4, or more
>> >levels of derivation. In fact, maybe you don't know how many
>> >levels there will be. For a chain of B->D1->D2->D3 I don't see how
>> >you can up front get the same effect this way. At the level of D2,
>> >base will skip the implementation of precious() in D1. At the
>> >level of D3, base will skip the implementation of precious() in D1
>> >and D2, etc. Chaining is lost beyond the first level. It is as if
>> >something is done in one direction, you want it done the other and
>> >you provide an example that does the reversion as long as there
>> >are only two entries. It is not truly a general solution.
>> >
>>
>> You misunderstand. It is precisely what the quote bellow says - it
>> saves you from coming up with new names. The same pattern can be
>> applied multiple times for the multi-derivation case you bring up,
>> it's just needs more method names.
>>
>> To extend my previous example:
>>
>> class Derived {
>> override precious() {
>> beforeDerived();
>> furtherPrecious();
>> afterDerived();
>> }
>> }
>>
>> class FurtherDerived : Derived {
>> void furtherPrecious() {...}
>> }
>>
>> Now, we get this call chain:
>> base.myPrecious() -> Derived.precious() ->
>> FurtherDerived.furtherPrecious()
>> This can be extended ad infinitum.
> [...]
>
> But the fact that you keep having to invent new names is itself the
> problem. It inevitably leads to horrible APIs that consist of names
> suffixed by numbers: precious(), precious1(), precious2(), precious3(),
> ad nauseaum. Method names that are suffixed by numbers are, as a rule,
> an indication of bad API design. They also make your derived classes
> immovable around the hierarchy, because to override their parent's
> method, they have to use the exact name that's introduced by the parent
> class, otherwise they risk accidentally short-circuiting part of the
> chain.
>
> Whereas BETA's unification of the entire chain of calls under a single
> name means that it's trivial to shuffle derived classes around the
> hierarchy -- you're guaranteed they only overload their immediate
> parent's method, and it stems the inevitable tide towards the horrible
> number-suffixed method names.
>
> I'm not saying that the BETA approach cannot be represented by the
> traditional approach -- they are functionally equivalent, as you point
> out. It's just that for some use cases, the BETA approach is superior.
> (All high-level languages compile to ASM, but that doesn't mean they add
> no value to programming directly in ASM.)
>
>
> T
Yeah, I agree with all of the above points and in a completely new language designed from scratch I may very well prefer the more principled Beta approach over the "regular" D approach.
But, In our current circumstances and given that D indeed already implements a functionally equivalent feature which we are not going to remove or replace, I just don't see adding this, in essence a duplicate feature, worth the complexity cost. As Anderi phrases that - it just doesn't pull its weight.
It's all a matter of trade-offs and we cannot and should not strive to put all possible feature combinations and implementation variants into D.
|
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to foobar | Am 20.12.2012 20:12, schrieb foobar:> > This argument is false. > > With the current "usual" design: > Base instance = new Derived(); > instance.method(); // #1 > > With Beta design: > Base instance = new Derived(); > instance.method(); // #2 > > In case #1, the call to method itself is virtual while the call to super > is not, whereas in case #2 the call to method is _not_ virtual, but the > call to inner is virtual. Either way you get one virtual call. The > difference is the direction of the calls. No its not, if you have a chain that is longer than just 1 you will have more virtual calls in the inner case. Kind Regards Benjamin Thaut |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to Benjamin Thaut | Am 20.12.2012 20:35, schrieb H. S. Teoh:>> >> So basically you suggest adding a language feature to cover up >> library developer's poorly designed API? IMO, if you want to provide >> a frozen API under the current inheritance design, you should use >> pimpl design pattern in the first place and not ask for features to >> solve the problem after the fact. > > Yeah, it doesn't make sense for the base class implementor to say "you > must call function X after you're done function Y". The base class API > should be *designed* so that function X *will* be called after function > Y, no matter what the derived class code does. > > OO code that breaks if methods are called in the wrong order (or certain > methods fail to get called) is broken by definition. The object's > internal state must remain consistent no matter what sequence its > methods are called. This is a fundamental tenet of OO design. > > > T > It makes perfect sense if you have certain preconditions. Something like 1) As little API changes as possible, so the customer can easily port to the new version 2) The original API was not designed by you, and never did anticipate that in the future the method can not be abstract anymore. If you have a codebase that is 10 years old, you will get such issues quite often. And unfortunately in C++ and D there is no good way to deal with such changes. (At least none that I know of) Kind Regards Benjamin Thaut |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | Am 20.12.2012 20:35, schrieb H. S. Teoh:>> >> So basically you suggest adding a language feature to cover up >> library developer's poorly designed API? IMO, if you want to provide >> a frozen API under the current inheritance design, you should use >> pimpl design pattern in the first place and not ask for features to >> solve the problem after the fact. > > Yeah, it doesn't make sense for the base class implementor to say "you > must call function X after you're done function Y". The base class API > should be *designed* so that function X *will* be called after function > Y, no matter what the derived class code does. > > OO code that breaks if methods are called in the wrong order (or certain > methods fail to get called) is broken by definition. The object's > internal state must remain consistent no matter what sequence its > methods are called. This is a fundamental tenet of OO design. > > > T > It makes perfect sense if you have certain preconditions. Something like 1) As little API changes as possible, so the customer can easily port to the new version 2) The original API was not designed by you, and never did anticipate that in the future the method can not be abstract anymore. If you have a codebase that is 10 years old, you will get such issues quite often. And unfortunately in C++ and D there is no good way to deal with such changes. (At least none that I know of) Kind Regards Benjamin Thaut |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to foobar | On Thu, Dec 20, 2012 at 09:49:40PM +0100, foobar wrote: > On Thursday, 20 December 2012 at 19:46:25 UTC, H. S. Teoh wrote: [...] > >But the fact that you keep having to invent new names is itself the problem. It inevitably leads to horrible APIs that consist of names suffixed by numbers: precious(), precious1(), precious2(), precious3(), ad nauseaum. Method names that are suffixed by numbers are, as a rule, an indication of bad API design. They also make your derived classes immovable around the hierarchy, because to override their parent's method, they have to use the exact name that's introduced by the parent class, otherwise they risk accidentally short-circuiting part of the chain. > > > >Whereas BETA's unification of the entire chain of calls under a single name means that it's trivial to shuffle derived classes around the hierarchy -- you're guaranteed they only overload their immediate parent's method, and it stems the inevitable tide towards the horrible number-suffixed method names. > > > >I'm not saying that the BETA approach cannot be represented by the traditional approach -- they are functionally equivalent, as you point out. It's just that for some use cases, the BETA approach is superior. (All high-level languages compile to ASM, but that doesn't mean they add no value to programming directly in ASM.) [...] > Yeah, I agree with all of the above points and in a completely new language designed from scratch I may very well prefer the more principled Beta approach over the "regular" D approach. > > But, In our current circumstances and given that D indeed already implements a functionally equivalent feature which we are not going to remove or replace, I just don't see adding this, in essence a duplicate feature, worth the complexity cost. As Anderi phrases that - it just doesn't pull its weight. > > It's all a matter of trade-offs and we cannot and should not strive to put all possible feature combinations and implementation variants into D. Agreed, I don't think we should be adding new stuff to D that can already be handled by existing mechanisms. D is already too complex for its own good -- too many features whose mutual interactions with other features aren't fully explored yet, which may turn out to have nasty side-effects. We don't need to be making this problem worse. T -- Why ask rhetorical questions? -- JC |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to Benjamin Thaut | On Thursday, 20 December 2012 at 20:58:40 UTC, Benjamin Thaut wrote:
> Am 20.12.2012 20:12, schrieb foobar:>
> > This argument is false.
> >
> > With the current "usual" design:
> > Base instance = new Derived();
> > instance.method(); // #1
> >
> > With Beta design:
> > Base instance = new Derived();
> > instance.method(); // #2
> >
> > In case #1, the call to method itself is virtual while the
> call to super
> > is not, whereas in case #2 the call to method is _not_
> virtual, but the
> > call to inner is virtual. Either way you get one virtual
> call. The
> > difference is the direction of the calls.
>
> No its not, if you have a chain that is longer than just 1 you will have more virtual calls in the inner case.
>
> Kind Regards
> Benjamin Thaut
well, if the library developer wants to gain full control of that long call chain than yes, it will come with a certain price. The same price if you design the same way in C++. In practice however how common are those chains and how long are they? As far as I'm aware, inheritance of concrete classes has mostly fallen out of favor nowadays so practically speaking this is still insignificant.
Btw, This reminds me the OO class in university which makes it very clear that the C++/Java/C#/etc variant of inheritance is inherently flawed. It conflates two orthogonal concerns - sub-typing and sub-classing.
Small-talk for example is dynamically typed so inheritance only serves the sub-classing concern. Interfaces in Java where designed to serve only the sub-typing concern.
I find Rust's trait system fascinating precisely because they realized this and did not repeat the same old mistake. They haven't addressed fully the sub-classing relation yet but I'm eagerly awaiting any developments in that area.
|
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On 12/20/2012 08:44 PM, H. S. Teoh wrote:
> ...
> They also make your derived classes
> immovable around the hierarchy, because to override their parent's
> method, they have to use the exact name that's introduced by the parent
> class, otherwise they risk accidentally short-circuiting part of the
> chain.
>...
>
That's what 'final' is for.
|
Copyright © 1999-2021 by the D Language Foundation