Who wants to chip in on an argument I'm having abo...
# cfml-general
a
Who wants to chip in on an argument I'm having about inheritance. 🙂 I think that "fully" overriding a method in a subclass is an abuse. 🧵
The code I'm reviewing has a baseclass
Copy code
// base class
component {
  function ready() {
    return true;
  }
}
Then there are two subclasses
Copy code
// foo 
component extends="base" {
  function ready() {
    return false;
  }
}

// bar 
component extends="base" {
  // no ready function as want it to return true
}
I really hate having a "default" function that you replace in your subclass if you want different behaviour. Thoughts?
BTW: I think it's fine to have a subclass method that calls
super.whatever()
and then specialises. I just don't think you should use inheritance to give default behaviour with the expectation a subclass doesn't event call the parent method
w
in certain apps i do a poor-man's-interface approach by having a base (parent) component declare a method that explicitly throws an error and therefore MUST be implemented in extending components, but in the scenario you're describing, i guess i don't see any issues or hangups by having base 'default' methods bleed into children unmodified. i'm assuming these are singletons of some type or are they actually beans?
👍 1
s
I would agree. The whole point of the base class is to confirm an assumption that all children inherit its definition and behavior. You can ADD behavior (
super.whatever()
) but if you're fully overriding it, your inheritance model is inaccurate (e.g. maybe you need an extra layer where foo descends from a simpler version that doesn't have ready())
w
i see
are there practical implications to doing so other than to violate some conceptual purity?
s
Having said that. If your child class inherits 99/100 elements of the parent with this one exception ... yeah, I don't think you get an award for purity
So I'd leave a snarky comment in there and be like 'it was either this or have yet another file open in your editor, yolo'
a
These are transients. The person is arguing that this is entirely the point of inheritance and I disagree strongly, but need to check I'm not talking nonsense. Inheritance for repeated functionality yes, common functionality yes. functionality that sometimes is never run as overridden - no
I think it makes the code less readable as by a function missing from the subclass I now need to go look at the parent to know what the heck it'll do (assuming I even know there is a missing method in the subclass)
I mean - it works - I just think it's lazy / sloppy code
w
then i'd agree that for transients adding methods vs overriding inherited ones is the only 'valid' thing you can do and toe the line, my 2c
s
Whatever ends up remaining in the code base, I would flunk the statement 'that's entirely the point of inheritance' when you're focusing on something that, observably, is not heritable
The base class should be the simplest possible definition of stuff that is common to all children
w
perhaps they just need to clarify this approach with respect to transients vs singleton use. i could see someone thinking the same applies to both
a
Yeah, to me inheritance is literally that. sub classes inherit from the parent, not I'll pick and choose when to inherit the parent behaviour
d
Either I don't understand this discussion (for which there's precedent 🙂), or I don't understand how inheritance is and isn't supposed to be used, or both. A pretty common case in my world is there's a preexisting Thing, then later there needs to be New Thing, which is The Same Only Different (tm). I could and in some cases do create a common base class, and never override any methods except ones you HAVE to override, because they explicitly throw if you don't -- comment would something like ALL child classes MUST implement this method. That requires separating out methods child classes may override from ones they won't. Going forward, if another method needs to be overridden, you have to move it out of Thing and into ThingBase, and for what? So you're Following The Rules? The word Override means replacing an aspect of the parent's functionality. Why does that word and concept even exist if we're not supposed to do that? Say Thing requires these three field to save, and NewThing requires two of those, plus two more that Thing doesn't have at all. What's evil about NewThing extending Thing, and overriding getFieldsToShow() and getRequiredFields()? NewThing might well implement those by calling the super version of the method and appending to it and/or removing some fields. If most fields are common to both levels, that seems a reasonable way to handle the few differences. If Thing adds a new field that's also in NewThing, just add it to Thing and you're done. If there's a ton a wildly diverging stuff, maybe NewThing isn't really a child variation of Thing in the first place. Am I missing the point? Abusing OO terminology? Generally out where the trains don't run?
w
i was under the impression that the original topic revolved around method inheritance specifically
a
Yes, my question is about the (mis)use of inheritance in my view and if I am justified in my view. Generally I will avoid inheritance and use composition, but there are times where I think it's valid.
I'm afraid I can't really comment on your scenario @Dave Merrill as it's a bit too abstract for me at the moment 😉
I'd also like to emphasise there is a difference between overriding a function in a subclass where the overriding method uses
super
and one that does not call
super
at all. It is the latter I object to @Dave Merrill
b
I'm not sure there is enough information here to make a pragmatic decision about what's right or wrong. Generally speaking, methods provide two benefits to a class • accessing data • behavior There's nothing wrong with a subclass overriding a method for the sake of modifying the behavior or the data that is accessed. I'm not entirely clear what you mean by "fully overriding" as that's not really a standard OO term and it's not clear what you mean by it. If the base class is "Dog" and the method in question is "bark" and the default implementation is
return "woof";
and you override the
Chihuahua
class only to have an implementation of
return "Yip"
, leaving all other dog classes to use the default, then I see no issue. This is a perfectly valid OO use case of inheritance and method overriding to provide a customized behavior for one subclass, while other sub classes can use the default. So long as your overridden methods implement the correct method contract, you should be fine. But I don't know if that's your exact scenario, which is where some pragmatism should be used. Your example is just a boolean, and if the real use case was more along the lines of a base class of
Product
which contained a field of
chargeTax
and defaulted to true, containing the getter and setter, and you wanted to simply turn off sales tax for certain products, I would be inclined to NOT override the
getChargeTax()
getter, but to instead, simply set it off in my concrete constructor class
Copy code
// PaperBackBook constructor
function init() {
  super.init();
  setChargeTax( false );
}
But, of course, this assumes a few things-- for starters that the base class is mutable! It also protects against code like
variables.chargeTax
which may bypass the getters entirely! I think all this stuff must be taken into account, and at the end of the day, there are prolly several "right" answers and potentially one or two "better" answers based on how your app is written and used. Now to address this > I think it makes the code less readable as by a function missing from the subclass I now need to go look at the parent to know what the heck it'll do IMO, this is just because CF has terrible tooling 🙂 In Java, this would not be an issue as your intellisense would answer all those questions right there in the code. This is the sort of stuff we plan to get working n our BoxLang VSCode extension.
🎯 3
d
I just don't think you should use inheritance to give default behaviour with the expectation a subclass doesn't event call the parent method
Maybe I need more coffee but I think you only sometimes want to call the parent method? Basically what Dave said about the fields (loved the trains comment BTW 😃)… sometimes it makes sense to call it, sometimes it makes sense to "replace" or "totally override" it, I think?
w
i think what he is saying is, using brad's example, were you to have a
Dog.cfc
(base) that has a
wagTail()
method, and you have a subclass
BostonTerrier.cfc
that effectively has no tail to wag, then that method is invalid in the base and should be implemented only in those extended objects where it applies. correct @aliaspooryorik?
d
I think that actually breaks the idea of inheritance, as you should be able to call the method, but it just shouldn't do anything (this is actually a good reason for not always calling the parent method)— otherwise you'd have to check every dog for a tail, as it were, or have a whole other subclass… DogWithTail or something… tho I guess that could make sense if you have a lot of tail-centric stuff? 🤔
a
Well I'm pleased I've started a debate 🙂 Let me hopefully clear up what I'm being pedantic about with some pseudo example code...
Copy code
// BaseBankAccount - I am never called directly only subclasses are instantiated
class {
  function getInterestRate() {
    return 0;
  }
}

// SaverBankAccount - I can be instantiated
class extends BaseBankAccount {
  function getInterestRate() {
    return 2;
  }
}

// StudentBankAccount - I can be instantiated
class extends BaseBankAccount {
  // there is no getInterestRate so BaseBankAccount.getInterestRate is used
  // this is what I don't like
}
So that StudentBankAccount is an example of what I don't like. I would much prefer it was written like this....
Copy code
// BaseBankAccount - I am never called directly only subclasses are instantiated
class {
  // there is no getInterestRate here as that is specific to each subclass
}

// SaverBankAccount - I can be instantiated
class extends BaseBankAccount {
  function getInterestRate() {
    return 2;
  }
}

// StudentBankAccount - I can be instantiated
class extends BaseBankAccount {
  function getInterestRate() {
    return 0;
  }
}
I hope that is clearer?
This would also be fine....
// this was an oversimplified example so doesn't add the discussion :)
w
this feels like semantics since in this limited example a getInterestRate() value of 0 denotes there is no interest rate (to me) and therefore there is no harm in leaving it inherited since it doesn't negate anything in the base. the last method in the last example seems of little benefit vs inheritance. but i know this is a contrived example
b
The base class would have the
abstract
modifier here, FWIW 🙂 Your example makes sense, and I would say I prolly disagree. So long as the default interest rate in the base class is valid, then I see no reason to add boilerplate and override it with the same value. Even more so, if the business wants to be able to change the default rate and have that change propagate instantly with a single line of code to all concrete classes not overriding. Your second example makes even less sense to me 🙂 I mean you could just return the super call, but again, that's just writing code for the sake of writing code. The only scenario in which I wouldn't want the base class's method being called is if it had a dummy value like
return -1
but in that case, you shouldn't; have put an implementation there, you should have made it an abstract method to enforce the override. #TeamDontOverrideWhatYouDontNeedTo
d
Yeah, I love that in the first example
StudentBankAccount
only needs to add things that aren't already in the base class, as that's one of the nicer bits about inheritance
a
Yes,
abstract
or even
private
on the base
BaseBankAccount
class would do the trick - but obviously wouldn't run so I'm trying to show code that you might happen upon in a code base and if you'd like it or not (on the basis that the next person to maintain the code is a serial killer who knows where you live!)
😂 1
d
And if not abstract, the thing already mentioned about throwing an exception in the base class's implementation also works imho
The first example is the easiest to maintain so I'd roll with that to prevent potential future murders 😆
a
My 3rd example with
super
is just rubbish - so I'm going to delete that as it's far too simplistic for a use case
b
FWIW, I dislike base methods that throw an exception. IMO, the only reason CFers started doing that was because CF didn't used to have a concept of abstract classes back in the "day", so we had to enforce the pattern manually. Now that CF allows a proper abstract class that the compiler/runtime enforces, there's no need to provide an implementation to a method that throws. Just declare the abstract method and let the engine do the rest for you.
Copy code
// concrete classes will be required to implement me
abstract function myAbstractMethod()
d
Doesn't abstract force you to implement the method though? Sometimes you might not really care, and use it like a "todo", neh?
Heh, I guess I could have read the comment— 😂 yeah it's common enough to use exceptions as "todo"'s FWIW, and I think that can be fine
Just depends on what it is you're doing
b
In java code, you'll often times see an exception in the concrete class where the implementation has been stubbed out. That's just temp code that you intend to change later, often times there to satisfy the compiler looking for a return value. That's not the same IMO as using an exception in an abstract class as a permanent solution for enforcing the implementation. Allowing the CF engine to enforce that on object creation provides a proactive check that the class implements everything as soon as its instantiated instead of deferring the check until later when you call the method.
d
I think it boils down to if you want to force an implementation or not — it doesn't have to be temporary, per se
No reason to force people to do boilerplate, unless there is, lol
a
I 100% agree with what @bdw429s sez. No problem not overriding a method in a subclass if there's no need to. If anything I'd say the opposite would be an issue... if yer overriding everything then I question whether the inheritance is sound, and whether perhaps an interface is what is needed. And yeah, faking abstract methods by having concrete methods that just throw an exception is less good than it could be. To agree with another sentiment expressed here... as with all situations where one is considering inheritance... check to make sure that's what's actually needed, and it's not a case of composition being a better solution. This doesn't seem to apply here, based on the trivial examples. Also: I can't see the bloody comment now, but I think @aliaspooryorik claims inheritance could be used to share behaviour. I would be really cautious about this. I have seen ppl create base classes with a bunch of utility functions which then subclasses extend in a kind of misguided "include" sort of way. that's not inheritance; and it's a sitter for composition. When using inheritance there must be a "is-a" relationship between base and subclasses.
a
No I do not claim that " inheritance could be used to share behaviour" at all. I really hope I didn't imply that.
I'm also not saying "yer overriding everything then I question whether the inheritance is sound, and whether perhaps an interface is what is needed". I would only have an abstract method or no method in the baseclass with an interface, no need to override as each concrete class handles it
a
Inheritance for repeated functionality yes
One thing to consider here is that CFML even allows for default method implementations of interface methods these days (following Java's lead, as does C#). This kinda demonstrates that having a concrete implementation of base class behaviour is just part of OOP.
a
Ah OK, can see how that could be interpreted, but not what I meant in my head. I clearly need to get better and getting stuff in my head into written form. Noted.
😜 1
d
FWIW I don't think the exception throwing route is "faking an abstract method" all the time… there's even an exception type in java
UnsupportedOperationException
kinda for this…
b
A class comprises two member categories, variables (state) and methods (behaviour). The discussion has so far focused on the inheritance of behaviour. The way I see it, behaviour does not - and cannot - satisfactorily resolve what @aliaspooryorik says he doesn't like, I think the way to go with this is member variables, not member methods. Let me use the bank account example to illustrate. The key inheritance question should therefore be: Does every bank account have an interestRate? If so, then interestRate should be a member variable of BaseBankAccount. It would then be up to the subclass to "behave" with the interestRate in any way it wants. For example, setInterestRate(), getInterestRate(), halveInterestRate(), compoundInterestRate(), and so on.