views:

483

answers:

4

Originally I had a design problem where I needed five subclasses of a superclass, where all but two would use the same generic method of doing things and the other two classes would need special handling. I wanted to avoid writing the method five times; two special cases and three identical ones.

So I had every class inherit SuperClass, and its doSomething() method, and had SubClassSpecial1 and SubClassSpecial2 override with their own doSomeThing method.

This was all fine until I wrote a method which looked something like

void fooBar(SuperClass obj) {
    obj.doSomething()
}

and it could be called as fooBar( new SubClassSpecial1() );

The problem is that the runtime class of the obj variable is now that of its superclass, and thus it will call the methods defined in the superclass. I could, of course, make an abstract doSometing() method in the superclass, and make each subclass implement its own version, but that would duplicate code in three of the classes. And I want to avoid doing that ...

I would lose any gain polymorphism gives if I had a lot of branching through

if(obj.getClass().getName() == "SubClassSpecial1" )  ((SubClassSpecial1) obj).doSomething()K;
else if ...

So what should I do in order to make the design more elegant and un-hackish?

A: 

Possible, you are to make this method virtual?

SMART_n
I guess a virtual method is the C++ equivalent of an abstract method in Java. I guess you are advocating this: http://www.javapractices.com/topic/TopicAction.do?Id=31The only problem is that doing that I would have to duplicate the code in the three classes where they are doing the same. That's not ideal ...
No, in Java all methods are virtual by default. So, my answer is incorrect. I thought it was C#. Sorry.
SMART_n
+4  A: 

I'm a bit confused by this part of your question:

Of course, what I wanted to do was have each object call its own version of doSomething(), but failed to realise that in order to do that, obj needed to be declared as one of the subclassed methods. And now it's a mess.

Ofcourse the declaration doesn't matter, the doSomething() method will always be invoked according to the runtime type of the class.

So I think what you were trying to do should work just fine, e.g. these declarations can all be used to pass to the foobar method:

SuperClass sc1 = new SubClassSpecial1();
SubClassSpecial2 sc2 = new SubClassSpecial2();
//etc..
NickDK
OK, I will try to edit that part and rephrase myself. What I meant was that I wanted the object to call its own (if overridden) method, not the one from its superclass. And the problem is that because it is declared (in the method declaration) as an instance of the superclass, it's runtime class type will be that of its superclass, and thus call the superclass method.Hope this makes some sense ...
No you're assumption is a bit off there, the class you use when instantiating a new object with the 'new' keyword determines the runtime type of your object. So in my example the SuperClass sc1 variable contains a reference to an object which runtime type is SubClassSpecial1. I now this can be a bit confusing, I hope you get it :-)
NickDK
Thanks, I began suspecting something was wrong after Terry's answer and found out Java did not do what I thought would be the "intuitive" way of doing it by writing some test code.Still find this behaviour a bit strange, but at least now I know :)
+2  A: 

What you've described should work fine as is.

Is obj.doSomething() actually calling the superclass' implementation? If it is, you're not overriding it properly. Check that you haven't changed the signature in your overridden versions.

Terry Wilcox
Well, isn't it supposed to do that?The runtime type of the object is declared as that of its superclass, and thus, it should call the superclass version.If I force ((SubClassSpecial1) obj).doSomething() it will call the subclassed version.Or is my assumptions wrong here on how inheritance and runtime classes work?
Hmm ... I wrote a test program that shows flaws in my assumptions about how changes on the Runtime type of an object works. I need to check into this before I get back ...
Your assumption is wrong. You don't need to cast obj to SubClassSpecial1 to get the SubClassSpecial1 doSomething() method, it will happen automatically. That's polymorphism. Try some tests.
Terry Wilcox
This is how java works. The only exception to this is during construction, if a super class constructor invokes an operation then the operation on the super class is used as the derived instance has not been constructed yet and it's fields would be invalid. If you need to do this then you will have to add an init operation to execute after construction.
vickirk
I did and you are indeed right. Found a subtle bug while doing it too. Thanks!
@vickirk: Even in a constructor the subclass's method is invoked. But the problem you describe (i.e., the subclass is not fully constructed yet) is the exact reason, why you should ONLY call private or final methods in a constructor.
janko
+1  A: 

When you have this:

void fooBar(SuperClass obj) {
    obj.doSomething();
}

then the compile-time type of obj is SuperClass. This means that the compiler is going to check that SuperClass has a doSomething() method.
At runtime you can substitute a subclass of SuperClass, this is the Liskov Substitution Principle. Method foobar() does not know, and should not know, what the runtime type of obj is, only that it derives from SuperClass and so doSomething() can be called.

As to your example:

fooBar( new SubClassSpecial1() );

In this case you happen to know that the runtime type of the parameter is SubClassSpecial1 which specifically overrides doSomething(). In all cases the right method is called.

A word about refactoring. You may want to consider refactoring your hierarchy.
Your base class SuperClass should define doSomething() as abstract. Your three classes that need the same implementation of doSomething() should inherit it from an intermediate base class which has that specific implementation. Your two special classes should inherit directly from SuperClass and have their own implementation of doSomething().

quamrana
Thank you, quamrama! For me this was the most informative of the answers, although my main gripe was already answered by someone else. You made me understand a lot more about how this works, and the refactoring tip is actually useful. Great answer!