December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to Benjamin Thaut | How is this proposal significantly different from the currently working: interface Base { final void chainMe () { if ( condition ) chainMe(); else doChainMe(); } protected void doChainMe(); } -- Chris NS |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to foobar | On Thursday, 20 December 2012 at 08:35:34 UTC, foobar wrote:
> On Thursday, 20 December 2012 at 08:12:54 UTC, Paulo Pinto wrote:
>> On Thursday, 20 December 2012 at 07:48:12 UTC, foobar wrote:
>>> On Thursday, 20 December 2012 at 00:07:49 UTC, H. S. Teoh wrote:
>>>> On Thu, Dec 20, 2012 at 12:11:34AM +0100, Andrej Mitrovic wrote:
>>>>> Some interesting blog post:
>>>>> http://journal.stuffwithstuff.com/2012/12/19/the-impoliteness-of-overriding-methods/
>>>>>
>>>>> It's a post about a common problem in class design, I've ran into it a
>>>>> few times in D too.
>>>> [...]
>>>>
>>>> Interesting concept. So "polite" overriding is simply a kind of override
>>>> where the derived class method has a vtable entry separate from the base
>>>> class method's entry (and thus needs explicit invocation from the base
>>>> class), whereas the usual "impolite" overriding is a kind of override
>>>> where the derived class method replaces the vtable entry of the base
>>>> class method.
>>>>
>>>> I can see use cases for both kinds of overriding.
>>>>
>>>> Interestingly enough, in the "polite" overriding case, the call to inner
>>>> is undefined until it's defined in the derived class. Which means that a
>>>> method that calls inner is a kind of pseudo-abstract method (since
>>>> abstract methods are those that *must* be implemented by the derived
>>>> class). So if we were to implement this in D, we could extend the
>>>> meaning of 'abstract':
>>>>
>>>> class Base {
>>>> // The current, usual overridable method
>>>> void replaceMe() {
>>>> writeln("A");
>>>> }
>>>>
>>>> // Abstract, because chain(chainMe) is undefined until
>>>> // the derived class implements it.
>>>> abstract void chainMe() {
>>>> writeln("B");
>>>> inner.chainMe(); // call derived class method
>>>> // 'inner' is a tentative
>>>> // keyword
>>>> writeln("C");
>>>> }
>>>> }
>>>>
>>>> class Derived : Base {
>>>> override replaceMe() {
>>>> writeln("D");
>>>> Base.replaceMe();
>>>> writeln("E");
>>>> }
>>>>
>>>> // Base.chainMe is abstract, so we have to implement
>>>> // this method.
>>>> override void chainMe() {
>>>> writeln("F");
>>>> }
>>>> }
>>>>
>>>> void main() {
>>>> auto d = new Derived();
>>>> d.replaceMe(); // prints D A E
>>>> d.chainMe(); // prints B F C
>>>> }
>>>>
>>>>
>>>> T
>>>
>>> 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.
>>
>> No because in your example you have to write Base already for that specific use case, whereas in BETA is up to the derived class to do as it please.
>
> Have you read the article?
> The Beta design (btw, it's the same in smalltalk - the article is wrong on that point) is to give control to the base class. The base class in beta is written specifically for that use case and the derived has no-control. That's the entire point of this design. It is basically a restriction to allow only overriding abstract methods (with a slightly more convenient syntax)
Me bad.
I just skimmed thru it yesterday, so I missed the main issue.
Sorry about that.
|
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to Benjamin Thaut | On Thursday, 20 December 2012 at 09:07:35 UTC, Benjamin Thaut wrote: > This polite overriding is a nice idiom. But it does not solve the most frequent problem I have at work (in C++) > > Assume you have a customer facing class they derive from to implement own features. > > abstract class Base > { > abstract void doStuff(); > } > I guess in the polite version you would make the call to the derived version here. The derived version wouldn't have to call super or anything. > The customer now derives from this class and implements doStuff. Because it is abstract he does not call the base implementation. > > Now you have to do some changes and suddenly the doStuff method is no longer abstract and does something important that should always be executed. > > abstract class Base > { > void doStuff(){ prepareSomething(); } > } > > The best you can do is put a line into the changelog "make sure to call the base implementation of Base.doStuff...". Which the customer most likely won't read and thus usually will fill a issue report with the Support deparment, because the code breaks. > > If there would be some kind of attribute supported by the compiler, such as that the compiler ensures that the base implementation is always called if the function is annotated with this attribute the customer would automatically be reminded by the compiler, and this would not be an issue. > > It could look something like this: > > abstract class Base > { > @mustcall void doStuff(){ prepareSomething(); } > } @mustcallbefore @mustcallafter Would these be automatically called in all derived versions? @autocallbefore @autocallafter > > Making a helper method that is called instead of doStuff is usually not an option, because the customer might have calls to doStuff in his code, which would still lead to a wrong execution path. |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to Benjamin Thaut | On Thursday, 20 December 2012 at 09:07:35 UTC, Benjamin Thaut wrote: > This polite overriding is a nice idiom. But it does not solve the most frequent problem I have at work (in C++) > > Assume you have a customer facing class they derive from to implement own features. > > abstract class Base > { > abstract void doStuff(); > } > > The customer now derives from this class and implements doStuff. Because it is abstract he does not call the base implementation. > > Now you have to do some changes and suddenly the doStuff method is no longer abstract and does something important that should always be executed. > > abstract class Base > { > void doStuff(){ prepareSomething(); } > } > > The best you can do is put a line into the changelog "make sure to call the base implementation of Base.doStuff...". Which the customer most likely won't read and thus usually will fill a issue report with the Support deparment, because the code breaks. > > If there would be some kind of attribute supported by the compiler, such as that the compiler ensures that the base implementation is always called if the function is annotated with this attribute the customer would automatically be reminded by the compiler, and this would not be an issue. > > It could look something like this: > > abstract class Base > { > @mustcall void doStuff(){ prepareSomething(); } > } > > Making a helper method that is called instead of doStuff is usually not an option, because the customer might have calls to doStuff in his code, which would still lead to a wrong execution path. Related to this, you might find interesting to read this paper about the Fragile Base Class problem, http://www.cas.mcmaster.ca/~emil/Publications_files/MikhajlovSekerinski98FragileBaseClassProblem.pdf Also related to this problem there was a school of though in the late 90's about component programming (similar to what Go uses) where OO only with composition is proposed. http://www.amazon.com/Component-Software-Object-Oriented-Programming-Addison-Wesley/dp/032175302X/ref=pd_sim_sbs_b_2 (First edition used Component Pascal, which incidentally has inheritance) I do realize this does not solve your problem, just sharing information. Paulo |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to Chris Nicholson-Sauls | Am 20.12.2012 10:42, schrieb Chris Nicholson-Sauls: > How is this proposal significantly different from the currently working: > > interface Base { > final void chainMe () { > if ( condition ) chainMe(); > else doChainMe(); > } > > protected void doChainMe(); > } > > > -- Chris NS Please read my post again. I explained why. This is not possible because you don't have control over the customers sourcecode. You only can change one part of the problem. And thus this approach is not possible. -- Kind Regards Benjamin Thaut |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to sclytrack | 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. -- Kind Regards Benjamin Thaut |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to sclytrack | Am 20.12.2012 10:44, schrieb sclytrack: > > I guess in the polite version you would make the call to the derived > version here. The derived version wouldn't have to call super or anything. > The problem I have with the inner method is performance. With the super method you know which method to call at compile time. The call can even be inlined. With the inner method you have to do another vtable lookup for each inner call. If implemented it should be possible to choose on a per method basis which kind of overriding policy is used, in my opinion. -- Kind Regards Benjamin Thaut |
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to foobar | 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.
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 Benjamin Thaut | On Thursday, 20 December 2012 at 10:22:56 UTC, Benjamin Thaut wrote:
> Am 20.12.2012 10:44, schrieb sclytrack:
>>
>> I guess in the polite version you would make the call to the derived
>> version here. The derived version wouldn't have to call super or anything.
>>
>
> The problem I have with the inner method is performance. With the super method you know which method to call at compile time. The call can even be inlined. With the inner method you have to do another vtable lookup for each inner call. If implemented it should be possible to choose on a per method basis which kind of overriding policy is used, in my opinion.
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.
|
December 20, 2012 Re: The impoliteness of overriding methods | ||||
---|---|---|---|---|
| ||||
Posted in reply to Benjamin Thaut | 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.
|
Copyright © 1999-2021 by the D Language Foundation