views:

114

answers:

5

Put simply: I want the following code to print "sub":

Element e = new SubElement();
print(e);
... 

private static void print(Element e) {
    System.out.println("e");
}

private static void print(SubElement e) {
    System.out.println("sub");
}

and i dont want to change print(Element e). so nothing like

private static void print(Element e) {
    if (e instanceof SubElement) {
        print((SubElement) e);
    } else {
        System.out.println("e");
    }
}

what i would like to do is

print(e.getClass().cast(e));

to automatically cast it to the real subclass and force the system to enter print(SubElement e). is this somehow possible?

+1  A: 

Yes. You can use Visitor pattern. However it's suitable for stablished well-defined hierarchies, because the Visitor interface you have to define needs a method for each type.

interface ElementVisitor {
   visit(Element e);
   visit(SubElement se);
}

class ElementerPrinter implements ElementVisitor {
   visit(Element e) { System.out.println("e"); }
   visit(SubElement e) { System.out.println("sub"); }
}

class Element {
  // here's the trick, Element knows that this is Element
  // and childs have to implement it!
  // if the parent-most class is an interface it force you to implement!
  accept(ElementVisitor visitor) { visitor.visit(this); } 
}

class SubElement {
  // here's the trick, Element knows that this is SubElement
  accept(ElementVisitor visitor) { visitor.visit(this); }
}
helios
actually im trying to change the visitor pattern for my needs;) i have a visitor interface which cant be changed so if i have to add new subclasses to the system i dont see any way to handle the new subclass seperatly without using instanceof in the original visit(Element e) method. i hoped that there was a way to force the system to automatically use the correct method without adding code to the Visitor interface.
Sponge
Well, if you have a visited element, the you do have the accept method and it's being invoked. When you subclass the Element the SubElement class can override the accept method to invoke visit(SubElement). But of course the already existent Visitor doesn't have the method. You can do ExtendedVisitor and the accept should do `if (visitor instanceof ExtendedVisitor) ((ExtendedVisitor)visitor).visit(this);`
helios
PS: not that nice. But it works and it integrates well with the already existent invoking of accept. If you were doing this from scratch maybe a Map<Class, Implementation> would be nicer.
helios
ok thanks for the infos, i still hope to find a way to use the reflection API to automatically call the correct method if its available to avoid such checks in the subclasses, but i didn't get it to work for now.
Sponge
+1  A: 

print() needs to become an instance method of Element, really. You're trying to imitate polymorphism in a hard way otherwise. If you wish to do that, you can't really avoid some series of if statements of mappings from Class to function objects. Why bother?

Sean Owen
Maybe its only a simplified example. But if it's the case I should do a polymorphic Element.getMyString() method, and let only the print method know about System.out.
helios
If fact, by his second example (the cast) I suspect he's trying to do *double dispatch* (match the correct method not only by the variable type but by the concrete holded instance too). Visitor pattern exists for that.
helios
+6  A: 

The overloaded method that is run is chosen at compile time so that is why the Element version is chosen rather than the SubElement version. What would seem more logical would be to have the Element or subclass contain the data that should be printed.

class Element {

    public String getName() {
        return "e";
    }
}

class SubElement extends Element {
    public String getName() {
        return "sub";
    }
}

and then in the print method:

private static void print(Element e) {
    System.out.println(e.getName());
}

Whether this is going to make sense will depend on what the Element class actually is and what the printed data represents.

krock
+1 for describing the problem first, and only then describing a solution.
ILMTitan
A: 

Are you able to push the difference of behaviour into the element classes?

Element e = new SubElement();
print(e);
... 

private static void print(Element e) {
    System.out.println(e.getMessageToPrint());
}

// no longer needed
//
//private static void print(SubElement e) {
//    System.out.println("sub");
//}

This way, SubElement can override the getMessageToPrint() method.

Or better still:

Element e = new SubElement();
e.print();
mattburns
A: 

I would choose a different approach. Either

  1. use polymorphism as suggested by others, extend Element to add a print() method (which can be overwritten by sub classes) or
  2. define a helper interface and use a combination of strategy and factory pattern:

Base Class

 public class Element{}

Derived Class

 public class SubElement extends Element{}

Helper Interface to print elements

public interface PrintHelper{
    void print(Element element);
}

Factory to get the best PrintHelper for a given element

public class PrintHelperFactory{

    private final Map<Class<? extends Element>, PrintHelper> registeredHelpers =
        new HashMap<Class<? extends Element>, PrintHelper>();

    // Register a PrintHelper for a given Element class.
    public void registerHelper(final Class<? extends Element> clazz,
      final PrintHelper helper){
        this.registeredHelpers.put(clazz, helper);
    }

    // Get the most specific PrintHelper for a given Element.
    public PrintHelper getHelperForElement(final Element element){
        Class<? extends Element> clazz = element.getClass();
        while(!Object.class.equals(clazz)){
            if(this.registeredHelpers.containsKey(clazz)){
                return this.registeredHelpers.get(clazz);
            }
            clazz = (Class<? extends Element>) clazz.getSuperclass();
        }
        return null;
    }

}

Client test class, run as Java Application

public class Main{

    public static void main(final String[] args){

        final PrintHelperFactory factory = new PrintHelperFactory();
        factory.registerHelper(Element.class, new PrintHelper(){
            @Override
            public void print(final Element element){
                System.out.println("Element");
            }
        });
        factory.registerHelper(SubElement.class, new PrintHelper(){
            @Override
            public void print(final Element element){
                System.out.println("Sub Element");
            }
        });

        // test it with an Element  
        final Element elem = new Element();
        factory.getHelperForElement(elem).print(elem);

        // test it with a sub class
        final Element sub = new SubElement();
        factory.getHelperForElement(sub).print(sub);

    }

}

Output

Element
Sub Element
seanizer