You can by overriding the method on apple or banana. If your method is on some other object, then yes, you cannot do this unless your programming language supports multiple dispatch.
There are ways to have your cake and eat it too, here, at least in some languages and patterns.
For example, in Go you could define "CheckType" as part of the interface contract, but group all implementors' versions of that method in the same file, calling out to nearby private helper functions for common logic.
Ruby's open classes and Rust's multiple "impl" blocks can also achieve similar behavior.
And yeah, sure, some folks will respond with "but go isn't OO", but that's silly. Modelling polymorphic behavior across different data-bearing objects which can be addressed as implementations of the same abstract type quacks, very loudly, like a duck in this case.
It's less "this is not OO" and more this is not inheritance, which is why a lot of people are saying you can find more elegant solutions (like this) rather than use inheritance for no clear benefit.
Heh? An apple is a fruit, you can pass it to any place expecting the former. Like, this is Liskov’s substitution’s one half.
With generics, you can be even more specific (co/in/contra-variance).
Fruit* fruit = new Apple();
ConsumeApple(fruit); // Doesn't work; requires Apple*
fruit = new Banana();
ConsumeBanana(fruit); // Doesn't work; requires Banana*
ConsumeFruit(fruit); // Okay, function signature is void(Fruit*)If OP wanted to be allowed to do whatever and just have the software fail at runtime, JS is right there.
I would never. I know that I'm holding a banana, I want to pass it to a method that receives a banana. But what I can't do is put my banana through a rotateFruit function first, because then Java will forget that it's a banana and start treating it as a fruit.
The runtime part is what I dislike. If I have a fruit which is an apple or a banana, I can't pass that to a method expecting an apple or banana. It can only be passed as a fruit.
> And if you want to avoid huge if-else statements, you should put the code for the special cases in the classes, not in each function that operates on them.
This is common OO wisdom that I strongly disagree with. For example, in my program I have a few types (Application, Abstraction, Variable, etc.), and a lot of transformations to perform on those types (TypeCheck, AnfConvert, ClosureConvert, LambdaLift, etc.).
I prefer to have all the type-checking code inside the TypeCheck module, and all the closure-converting code inside the ClosureConvert module. I'd take the "huge if-else" statements inside TypeCheck rather than scatter typechecking across all my datatypes.