What you are doing is perfectly legitimate. Do not pay attention to the naysayers who merely reiterate dogma because they read it in some books. Dogma has no place in engineering.
I have employed the same mechanism a couple of times, and I can say with confidence that the java runtime could have also done the same thing in at least one place that I can think of, thus improving performance, usability, and readability of code that uses it.
Take for example java.lang.reflect.Member
, which is the base of java.lang.reflect.Field
and java.lang.reflect.Method
. (The actual hierarchy is a bit more complicated than that, but that's irrelevant.) Fields and methods are vastly different animals: one has a value that you can get or set, while the other has no such thing, but it can be invoked with a number of parameters and it may return a value. So, fields and methods are both members, but the things you can do with them are about as different from each other as making sandwiches vs. contacting aliens.
Now, when writing code that uses reflection we very often have a Member
in our hands, and we know that it is either a Method
or a Field
, (or, rarely, something else,) and yet we have to do all the tedious instanceof
to figure out precisely what it is and then we have to cast it to get a proper reference to it. (And this is not only tedious, but it also does not perform very well.) The Method
class could have very easily implemented the pattern that you are describing, thus making the life of thousands of programmers easier.
Of course, this technique is only viable in small, well-defined hierarchies of closely coupled classes that you have (and will always have) source-level control of: you don't want to be doing such a thing if your class hierarchy is liable to be extended by people who are not at liberty to refactor the base class.
Here is how what I have done differs from what you have done:
The base class provides a default implementation for the entire asDerivedClass()
family of methods, having each one of them return null
.
Each derived class only overrides one of the asDerivedClass()
methods, returning this
instead of null
. It does not override any of the rest, nor does it want to to know anything about them. So, no IllegalStateException
s are thrown.
The base class also provides final
implementations for the entire isDerivedClass()
family of methods, coded as follows: return asDerivedClass() != null;
This way, the number of methods that need to be overriden by derived classes is minimized.
I have not been using @Deprecated
in this mechanism because I did not think of it. Now that you gave me the idea, I will put it to use, thanks!
C# has a related mechanism built-in via the use of the as
keyword. In C# you can say DerivedClass derivedInstance = baseInstance as DerivedClass
and you will get a reference to a DerivedClass
if your baseInstance
was of that class, or null
if it was not. This (theoretically) performs better than is
followed by cast, (is
is the admittedly better named C# keyword for instanceof
,) but the custom mechanism that we have been hand-crafting performs even better: the pair of instanceof
-and-cast operations of Java, as well as the as
operator of C# do not perform as fast as the single virtual method call of our custom approach.
I hereby put forth the proposition that this technique should be declared to be a pattern and that a nice name should be found for it.
Gee, thanks for the downvotes!
A summary of the controversy, to save you from the trouble of reading the comments:
People's objection appears to be that the original design was wrong, meaning that you should never have vastly different classes deriving from a common base class, or that even if you do, the code which uses such a hierarchy should never be in the position of having a base reference and needing to figure out the derived class. Therefore, they say, the self-casting mechanism proposed by this question and by my answer, which improves the use of the original design, should never have been necessary in the first place. (They don't really say anything about the self-casting mechanism itself, they only complain about the nature of designs that the mechanism is meant to be applied to.)
However, in the example above I have already shown that the creators of the java runtime did in fact choose precisely such a design for the java.lang.reflect.Member
, Field
, Method
hierarchy, and in the comments below I also show that the creators of the C# runtime independently arrived at an equivalent design for the System.Reflection.MemberInfo
, FieldInfo
, MethodInfo
hierarchy. So, these are two different real world scenarios which are sitting right under everyone's nose and which have demonstrably workable solutions using precisely such designs.
That's what all the following comments boil down to. The self-casting mechanism is hardly mentioned.
instanceof
, in a way that requires lots of typing, is error-prone, and makes it hard to add more subclasses.Sub1
andSub2
cannot be used interchangeably, then why do you treat them as such? Why not keep track of your 'sandwich-makers' and 'alien-contacters' separately?