Forcing all derived classes to implement function

Monkey Forums/Monkey Programming/Forcing all derived classes to implement function

Peeling(Posted 2015) [#1]
An abstract method declaration forces directly derived classes to implement that method. However, indirectly derived classes are NOT obliged to implement that method:

Class A
Method X() Abstract
End

Class B Extends A
Method X()
End
End

Class C Extends B
'doesn't need to override X
End

It would be incredibly useful to me if there were a way to force a compile error if C does not provide its own unique declaration of X. I doubt it's possible, but I thought I would ask.


Samah(Posted 2015) [#2]
This is the intended functionality of abstract methods. It only needs to be implemented once in the hierarchy.
If you need that functionality, it seems to me that you need to rethink your class structure.


Peeling(Posted 2015) [#3]
I don't think there's anything wrong with the class structure :)

I appreciate that 'abstract' is working as intended. It would still be useful to enforce implementation in every class in the hierarchy.

As a fer-instance, suppose you wanted to implement a simple runtime class identifier method without all the overhead of reflection:

Method ClassName:String()
Return "TheClassName"
End

It would be nice to poke the user with an error if he forgot to implement ClassName in a derived class.

My interest in the feature is a bit more involved, but that's the basic idea.


Gerry Quinn(Posted 2015) [#4]
It would be incorrect to force C to override X. because B.X is already available to C so it is unnecessary,


Peeling(Posted 2015) [#5]
Certainly it would be incorrect for the 'abstract' keyword to force C to override X, because 'abstract' already has a well-defined function.

But there's nothing incorrect about having a keyword (eg 'Unique Abstract' or 'Bespoke') that does force C to override X. It would be very useful in cases where it's vital for all derived classes to implement overrides because the parent class's implementation is guaranteed to be undesirable (as in the 'ClassName' example above)


Pharmhaus(Posted 2015) [#6]
The only thing you can do is to declare the Method in B abstract again. But this is only useful if B's Method X() is never used by anybody.

Class B Extends A
    Method X() Abstract
End


If your implementation of B needs X() your only chance is to use the decorator pattern and create a Class D which extends B where you define X() as abstract again.
e.g.

Class B Extends A
    Method X()
    End
End

Class D Extends B
    Method X() Abstract
End


All further classes would use D as a starting point.


skid(Posted 2015) [#7]
I try and avoid ever overriding a concrete class.

If B and C need to share some logic then consider implementing AA an abstract class derived from A containing the common logic allowing you to avoid deriving a class from a concrete class.

Take for example the class hierachy in biology. Where instances share common traits there is a class hierarchy (all abstract) that contains all shared stuff so that cats never derive from dogs:




Samah(Posted 2015) [#8]
@skid: I try and avoid ever overriding a concrete class.

I don't avoid it entirely, I just try to design my class structure in such a way that it's unnecessary.
As therevills will attest, I love abstract classes and interfaces. :)
It allows you to encapsulate more code by hiding concrete implementations from developers.


Peeling(Posted 2015) [#9]
Thanks all :)

Pharmhaus: It's a really clever idea! I think the downside is that it imposes a burden on the user almost as onerous as remembering to override the relevant functions, and there's no way to enforce derivation from AA rather than A.

Also, while it works for the simple "ClassName" example, in my case there are methods where it is mandatory to both override X *and* have the override call the parent class's X, which your solution would not permit.

skid: Looking through the code, in most cases non-leaf classes ARE abstract, in practice if not in name. Unfortunately, it's in precisely those exceptional cases where classes are derived from other concrete classes where I need to ensure certain functions are overridden. Not only that, but there are functions with bodies in the abstract classes which must be overridden for things to work properly.

To give you some idea of one of the things I'm doing: I've created a 'shelvable' interface for templated object pooling. Calling 'Shelve' places the object in a resource pool. However, if you forget to implement "Shelve" the parent class's function will place a derived class in the parent class's resource pool, with hilarious consequences next time it gets pulled out for re-use.

I've also created an interface for Unity-style instantiation of a hierarchy of objects. Here again, forgetting to override a function will result in craziness: an instance of the parent class will be created in the new hierarchy with the relevant values copied across, rather than the derived class :)


Samah(Posted 2015) [#10]
@Peeling: To give you some idea of one of the things I'm doing: I've created a 'shelvable' interface for templated object pooling. Calling 'Shelve' places the object in a resource pool. However, if you forget to implement "Shelve" the parent class's function will place a derived class in the parent class's resource pool, with hilarious consequences next time it gets pulled out for re-use.

It sounds more like you need to rethink your pooling implementation. Perhaps look at the way the BRL Pool and Diddy's DiddyPool classes work.


Gerry Quinn(Posted 2015) [#11]
Overriding concrete classes can be useful. For example, MFC (Microsoft Foundation Classes) were a C++ wrapper for the Windows GUI. A CWnd object was a basic window, and all controls were derived from it, sometimes with a hierarchy of classes, e.g. a CButton derived from CWnd and was a text button, and a CBitmapButton derived from CButton and had an image.


skid(Posted 2015) [#12]
I don't think CWnd is a good example.

IMHO MFC would have been better to keep CWnd abstract and provide a concrete "CWindow" class to avoid all derived classes inheriting methods they don't support such as menu and titlebar controls.


Samah(Posted 2015) [#13]
@skid: ...to avoid all derived classes inheriting methods they don't support such as menu and titlebar controls

Exactly this. If your design causes classes to inherit things they don't need, you're doing it wrong.

Edit: If you find code in Diddy that does this, it's because I was being extremely lazy at the time, not being hypocritical. :)
Edit 2: Or, I may have had to retrofit stuff to avoid breaking other people's code.


Peeling(Posted 2015) [#14]
Hi again,

I've just looked at diddypool (it wasn't in the diddy in my version of Monkey so I didn't know about it - long story; don't worry about it).

Correct me if I'm wrong, but it looks as though it's necessary to return objects to the pool thus:

myobjectpool.Free(object)

This is fine if the code freeing up the object knows which pool to put it in. If that isn't the case, you would need to provide a wrapper along these lines:

Class MyClass implements FreeSelfInterface

global myPool:DiddyPool<MyClass> = new DiddyPool<MyClass>(100)

Method FreeSelf()
myPool.Free(Self)
End

End


Since I'm pooling and instancing hierarchies of objects (imagine a simpler version of Unity scene graphs and components), objects need to be able to pool their children without knowing what type they are, which leaves me back where I am now: if I forget to implement 'FreeSelf', the parent class's implementation will get called. I can mitigate this by keeping FreeSelf abstract wherever possible, but I'm still exposed to the same risk with derivations from concrete classes.

The pooling system I'm using looks like this:

Class AResourcePool<T>

Global shelved:T[]
Global shelvedCount:Int = 0

Function Allocate:T()
return either New T() or the top of the shelved array (could use a stack instead)
End

Function Shelve:Bool(t:T)
Put t on the shelf and call reset
End

End


Class ThingToShelve implements Shelvable

Method Shelve()
AResourcePool<ThingToShelve>.Shelve(Self)
End

Method Reset()
reset to defaults
End

End


So it's pretty similar, although without all the diddy niceties. There's no need to initialise pools manually; you can just call AResourcePool<Blah>.Allocate() and the compiler takes care of generating the relevant global arrays.

I don't need to keep track of an array of allocated instances for iterating over, because they're already getting plugged into a scene graph.

The fact that allocated instances can be GC if Shelve isn't called is... meh. On the one hand it's sloppy and could dilute the efficiency of the pool if it happens a lot. On the other hand it's nice to know that it's impossible for some obscure edge case to result in the pool clogging up with orphaned objects when the game is out in the wild :)


ImmutableOctet(SKNG)(Posted 2015) [#15]
@Peeling: I'm generally against using globals for this; I consider doing something like this to be bad practice if it's not at least abstracted from such a rough back-end.

Anyway, I'm definitely for an "Unfinished" keyword, or something to that effect. Personally, I wouldn't mind such a feature, but to tell you the truth, you should probably provide/follow documentation regarding implementation details. For example, "requiring" (More so expecting) an inheriting class to "call up" to their super-class's implementation. This is something I've dealt with before, so it would definitely help keep things neat. The name "Unfinished" (As I used as an example) would probably be misleading, as I think this would need to be a "passive" version of 'Abstract'. My thought is that that kind of feature should allow the implementation to be used if the class it's within isn't abstract. If it is abstract, then I think it should be required to override. Just an idea, I thought I'd bring it up. I'd want it to work like this mainly for constructor-styled behavior. The thing is, this isn't really necessary to me. Sure, it would be nice for specific circumstances, but it's not really something to care about. This is really an implementation detail that your own standards or documentation should comment upon. You can't really blame a class for not overriding/displaying what its name is, you can only discourage misleading implementations.

Also, your idea of providing a problematic implementation, then forcing inheriting classes to re-implement it anyway, is backwards to me. Shouldn't you have a separate method containing such a "faulty" implementation? Or better yet, split it into several methods, then leave usage up to the user. I don't see why you can't make the method abstract, then provide the tools to implement it.


Peeling(Posted 2015) [#16]
@ImmutableOctet: I can understand not wanting to use globals, but here's the problem:

1. I have to be able to pool objects without knowing what type they are, therefore:
2. The object has to be able to pool itself, which means it has to have access to the pool, which means either:
a global in the AResourcePool
OR
a global in the pooled class
OR
a field in the pooled class that has to be set up every time it is instanced.

At which point, you have to ask: where is the code calling thePool.Allocate() getting thePool from?

It's just pushing the sprout around the plate and you end up eating it anyway :)

As to the "unfinished" keyword - absolutely. It's a 'nice to have in a specific circumstances' only; that's why I was asking if there was such a thing, not FOR it :)

The last point, about an implementation being problematic but needing to be called anyway - I HAVE gone back and tidied that up, because it was just too ugly. I've split it thus:

Method MustBeImplementedInEveryDerivedClass()
do the class-specific thing that would break everything if the super class version were called
MustBeCalledOnSuperclass()
End

Method MustBeCalledOnSuperclass()
Super.MustBeCalledOnSuperclass()
End

This makes 'MustBeCalledOnSuperclass' optional, which is good if it doesn't need to do anything important.


Samah(Posted 2015) [#17]
To me this is all stuff that's completely unnecessary with good design. I've been trying to think of an easier way to implement your solution, but I'm starting to think it's just the wrong way to go about it.

@Peeling: It's just pushing the sprout around the plate and you end up eating it anyway :)

I've never heard this phrase before. The problem is that I'd end up just feeding the sprout to the dog. :)


Peeling(Posted 2015) [#18]
Believe me, I'm under no illusions about the shortcomings of my implementation. But given what it has to do - provide an extensible scene-graph hierarchy supporting abstract pooling and deep cloning in a GC language, I suspect it might be the worst way of doing it apart from all the others, to paraphrase Churchill :) I'm open to suggestions, though.

I've worked with other scene-graph library source in the past, code that really went out of its way to do things properly, and as a result only two or three people in the company actually understood it well enough to extend its functionality. It also took the original author - who left the company - two years to write, and the 'simple' class declarations were underpinned by layer upon layer of opaque macros. By comparison, the mental load of "Don't forget these three mostly-boilerplate methods" doesn't seem all that onerous. It's not something I'd release as a Monkey library by any means, but I think it meets our needs (factoring in learning curve, dev time, performance on mobile devices etc) appropriately.


Gerry Quinn(Posted 2015) [#19]
In practice, I never had problems due to trying to use a menu in CButton or some such thing. It's hard to imagine a scenario where one would accidentally do this, or when the head-scratching when it doesn't work is going to be worse than the head-scratching about why one tried to do it in the first place.

Sometimes the best can be the enemy of the good.

I think the sin we (probably) all commit of writing really horrible code just to get something up and running can lead to us trying too hard to polish our libraries, even if they are probably not really for the generations anyway.