As you said yourself, circumstances called for that ordering. There are no stylistic "rules" as to the order in which a parent's implementation has to be called, it fully depends on implementation-specific factors. You may even find instances in which the parent implementation has to be called conditionally.
The only special cases when it makes sense for parent implementations to always be called first (or last) are constructors (and destructors). In these cases, ordering is important so that, by the time thing1
and thing2
(in your example) execute, your object's ancestry has already been fully constructed (or not yet destructed.) In all other cases (regular methods) it is up to you to pick the ordering which best suits your business logic needs.
This having been said, coming back to your example, I'd worry more about whether you want work()
to be virtual
or not in those languages where this is an option. :)
class BasicLogger {
BasicLogger() {
// open log file
}
virtual function log(string s) {
// write to log file
}
}
class TimestampedLogger extends BasicLogger {
TimestampedLogger () {
// consructor calling order enforced in most OOP languages
BasicLogger(); // must be called first to open log file,
log("logging started"); // or else writing to the log will fail
}
virtual function log(string s) {
s = timestamp() + ": " + s;
super.log(s); // nothing wrong with this
}
}
class ConditionalLogger extends TimestampedLogger {
ConditionalLogger () {
TimestampedLogger();
log("log level is " + logLevel());
}
virtual function log(string s) {
if (logLevel() > INFO) {
super.log(s); // nothing wrong with this either
}
}
}