The impoliteness of overriding methods
journal.stuffwithstuff.com
journal.stuffwithstuff.com
Generic functions and their associated methods allow :before, :after and :around qualifiers to be added to specify the order in which they are run without the need of creating secondary method names for each layer of subclasses. For base class methods that need code to be run before or after the more-specific subclass method is called, a :before/:after method(s) is written that will be executed before the more-specific subclass "overridden" unqualified method is called. The developer of the subclass doesn't have to care/know that the base class is still doing some work there.
Conversely, if this base :before method needs to be modified, it too can be overridden with a corresponding :before qualified method (with/or without a call back to the base :before method via call-next-method) or even totally shadowed by an :around qualified method in the subclass.
This seems to be the best of both worlds. The original base class developer can specify code that needs to be executed for each overridden method without the sub-class developer needing to be aware of it without handcuffing the sub-class developer in handling edge-cases that the base-class developer didn't foresee.
Here is the example code that implements this:
(defclass game-object ()
((x :accessor x :initarg :x :initform 0)
(y :accessor y :initarg :y :initform 0)))
(defmethod set-transform (renderer x y)
;; set the renderer coordinates here.
)
(defmethod draw-image (renderer image)
;; draw image here
)
(defgeneric render (game-object renderer)
(:documentation "Render GAME-OBJECT to display using RENDERER."))
(defmethod render :before (game-object renderer)
(set-transform renderer (x game-object) (y game-object)))
(defmethod render (game-object renderer)
(draw-image renderer game-object))
;; Class where the :before base class method just runs
(defclass scary-monster (game-object)
((image :accessor image :initarg :image)))
(defmethod render ((game-object scary-monster) renderer)
(draw-image renderer (image game-object)))
;; Class where the :before base class method is prevented from running
;; to prevent some undesirable action.
(defclass happy-monster (game-object)
((image :accessor image :initarg :image)))
(defmethod render :around ((game-object happy-monster) renderer)
(draw-image renderer (image game-object)))
;; REPL session with the RENDER, SET-TRANSFORM and DRAW-IMAGE methods traced
CL-USER> (render (make-instance 'scary-monster :x 1 :y 2 :image 'a) nil)
0: (RENDER #<SCARY-MONSTER {2456D3F1}> NIL)
1: (SET-TRANSFORM NIL 1 2)
1: SET-TRANSFORM returned NIL
1: (DRAW-IMAGE NIL A)
1: DRAW-IMAGE returned NIL
0: RENDER returned NIL
NIL
CL-USER> (render (make-instance 'happy-monster :x 3 :y 4 :image 'b) nil)
0: (RENDER #<HAPPY-MONSTER {246A1689}> NIL)
1: (DRAW-IMAGE NIL B)
1: DRAW-IMAGE returned NIL
0: RENDER returned NIL
NILOne metaphor that I like to use in OO is that inheritance is best used to "fill in holes." Imagine that an abstract class is a class with holes in it (i.e., the abstract methods). Subclassing fills in the holes and gives you a complete class. That is really the best use of inheritance. If you override, you have a higher chance of breaking behavior via a Liskov Substitution violation.
More "traditional" OO like C++ or Java let the developer choose. The "traditional" way gives you more flexibility and more ways to shoot yourself in your foot.
The template pattern was when I finally "got" design patterns, realizing that they were ways of describing and communicating (some) things that I was already doing.
In C++, the "Non-Virtual Interface" (NVI) design pattern recommends that interface classes declare nonvirtual public functions that delegate to private pure virtual functions to be implemented by derived classes. A base class destructor should be either public and virtual, or protected and nonvirtual. NVI allows the base class to add pre- and post-condition assertions to test the derived implementation classes. NVI should also alleviate the "fragile base class" problem with C++ binary compatibility.
https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtua...
The Entity-Component-System architecture I think largely solves my problem, though. After I finish my current game I plan on reflect on better ways to structure the data, so it's good to have this idea in mind. Thanks.
Here is one of the examples in Perl/Moose:
package GameObject {
use Moose;
has ['x', 'y'] => (is => 'rw', isa => 'Num');
sub render {
my ($self, $renderer) = @_;
$renderer->set_transform( $self->x, $self->y );
inner;
}
}
package ScaryMonster {
use Moose;
extends 'GameObject';
augment render => sub {
my ($self, $renderer) = @_;
$renderer->draw_image(Images->scary_monster);
};
}
refs:- https://metacpan.org/module/DOY/Moose-2.0604/lib/Moose/Manua...
- https://metacpan.org/module/DOY/Moose-2.0604/lib/Moose/Cookb...
Do you think it would be wise to implement inner() (perhaps alongside the existing super()) for Dart?
If so, would there be a decent way to do the implementation for the compile-to-JS version, given that there's no native JavaScript way refer to things down the prototype chain instead of up?
I think it would be neat, but it's very unlikely to happen. I'm not one of the language designers. Lars and Kasper are and they've both implemented BETA VMs, so they're certainly aware of inner() and decided not to go in that direction.
If so, would there be a decent way to do the implementation for the compile-to-JS version, given that there's no native JavaScript way refer to things down the prototype chain instead of up?
If you can statically tell your inheritance chains (i.e. at compile time you know what your superclass is), it should be pretty straightforward, I think.
class A {
foo() {
before();
inner();
after();
}
}
You'd compile the above to something like: function A() {}
A.prototype.foo = function() {
before();
this.$A_foo_inner();
after();
}
A.prototype.$A_foo_inner = function() {}
And a subclass like: class B extends A {
foo() {
console.log('In B!');
inner();
}
}
Gets compiled like: function B() {}
B.prototype.$A_foo_inner = function() {
console.log('In B!');
this.$B_foo_inner();
}
B.prototype.$B_foo_inner = function() {}
I think something like that would work, but I haven't put much thought into it. The critical bit is being able to tell at compile time that the foo() in B is an override so that you know to compile it to $A_foo_inner and not just foo().For a method "xyz", look for whether there's currently an "xyz_inner" and call that if so. It's easy enough for the Dart compiler to do the resulting paperwork; it's what compilers do. (Yes, it is yet more complicated a bit to keep going down to a second layer of inheritance but it seems like it could be done, just more paperwork. And of course the symbol names are arbitrary, it need not be "inner".)
var account = {
balance: undefined,
deposit: function(amount) {
return balance += amount;
}...
}Dart does indeed allow you to omit "this." which is really really nice.
Less utility, but simpler to explain and already implemented, at least in some languages.