May 24

Imagining a method which receives as a parameter a type Object instance and needs to perform some action according to its type is not difficult. In fact, there are several design patterns that, when used in combination, help solve just that. However, this post is about adding a new switch..case construct to prevent some boiler-plate caused by these patterns.

For example, imagine a simple addition operation, which should behave well for Integer instances and Complex numbers, and throw an exception for any other input:

Object add(Object a, Object b) {
  if (a.getClass() != b.getClass())
    throw new IllegalArgumentException("Not same type");

  if (a.getClass() == Integer.class) {
    return ... // do int addition
  } else if (a.getClass() == Complex.class) {
    return ... // do complex addition
  } else {
    throw new IllegalArgumentException("Unsupported type");
  }
}

Design patterns to the rescue

In order to allow further extensions, an interface for the addition operation can be created:

interface Addition {
  Object add(Object a, Object b);
}

and a strategy to create the correct instance, which accepts the two parameters, is created as well:

class AdditionStrategy {
  static Addition createByType(Class type) { ... }
}

and obviously implement the interface for Complex and Integer instances. This reduces the original code to:

Object add(Object a, Object b) {
  if (a.getClass() != b.getClass())
    throw new IllegalArgumentException("Not same type");

  return AdditionStrategy.createByType(a.getClass()).add(a, b);
}

extension-ability achieved! But how is that strategy implemented? It could be implemented using a bunch of if statements as well, but since we actually have an object to define the action to be taken now, we can use a Map instead:

class AdditionStrategy {
  Map<Class , Addition> additions = ...;

  static Addition createByType(Class type) {
    Addition res = additions.get(type);

    if (res == null)
      throw new IllegalArgumentException("Unsupported type");

    return res;
  }
}

This gets a bit complicated if we want to check whether the instance class extends one of the class types in our map (for example, if we wanted the same add operation for anything extending the Number class):

class AdditionStrategy {
  Map<Class , Addition> additions = ...;

  static Addition createByType(Class type) {
    for (Map.Entry<Class , Addition> entry : additions.entrySet()) {
      if (entry.getKey().isAssignableFrom(type)) {
        return entry.getValue();
      }
    }

    throw new IllegalArgumentException("Unsupported type");
  }
}

New construct

So far, so good – and probably nothing new as well. I’m wondering is if even that can be removed, as this is clearly boiler-plate code. The Java language has done a great deal of removing boiler-plate code through means of compiler decisions: enums, strings and for-each loops are just a few of those. We could see something like the following for our factory class:

class AdditionStrategy {
  static Addition createByType(Class type) {
    switch (type) {
      case Complex: return new ComplexAddition();
      case extends Number: return new NumberAddition();
      default: throw new IllegalArgumentException("Unsupported type");
    }
  }
}

Notice the case extends which queries whether the class extends the Number type, and not just equal to it. This is even better than the Map option, as the developer is given a choice to check for extension in some cases and not to do so in others.

What do you think?

Related Posts with Thumbnails
Share

10 Responses to “Switch by class type”

  1. gonzalo Says:

    I think this should be solved with polymorphism.

  2. Casper Bang Says:

    gonzalo. That easy to think but I can’t see how it applies, the idea here is to have a pluggable mechanism. Coincidentally I wrote a little about this same pattern earlier this month (http://coffeecokeandcode.blogspot.com/2008/05/type-strategy-pattern.html). Could you provide an example where polymorphism solves this problem of i.e. assigning renderers for Swing components at runtime?

  3. Reinier Zwitserloot Says:

    I really don’t like it. The switch statement is an ugly duckling as is. Removing pointless limits (such as no Strings in switch statements) is a good thing, but building upon the switch statement to create new territory is not a good thing.

    Besides, this is clearly better solved in the API domain. I’ll leave the actual code of this API as an exercise to the reader, but you can write it with java+BGGA:

    Utilities.classSwitch()
    .caseIs(Complex.class) {=> return ComplexAdder(); }
    .caseExtends(Number.class) {=> return NumberAdder(); }
    .switch(type);

    In CICE it’s not going to be as pretty. Even in vanilla java6 you can accomplish more than enough to make me wonder at what could possibly drive you to consider this use case to warrant LANGUAGE extensions?

  4. gonzalo Says:

    Maybe I’m getting something very wrong here, but, to me it looks like something that can be handled with basic OO constructs. Just to keep the ‘addition operation’ topic, here’s a very näive impl. of the same behavior (I’m not sure it builds, but you get the idea):

    http://pastebin.com/m7e1208f4

    NOTE: Sorry if the post is duplicated, I got a very nasty SQL exception the first time i’ve tried to submit it (“Lost connection to MySQL server during query”).

  5. Dimitris Andreou Says:

    I agree with gonzalo, i.e. there is the possibility to move the mapping in the concrete classes themselves (and you get the extension semantics for free) instead of an external map.

    Or if you want to just minimally touch the concrete classes, and having mentioned design patterns, a visitor is another obvious solution.

  6. Rakesh Juyal Says:

    Article is good but readability is poor, certainly because of your font selection. Just a suggestion, Please change the font. ;)

  7. Aviad Says:

    @Rakesh: what browser are you using? On Safari it looks good… What font would you suggest?

  8. to domain name Says:

    hi guys…

    hi guysI would like to thank you for the efforts you have made in writing this article. I am hoping the same best work from you in the future as well and i have start my own blog now, , thanks for your effort…

  9. Mark Carroll Says:

    I’m reminded of Modula-3′s TYPECASE. The automatic casting it does is convenient.

  10. Mike DOuglass Says:

    I remember the Modula-3 typecase.

    Yes – you can build this yourself but as I recall – Modula-3 had a lot of support for typecase – including providing an index to every type in the system.

    I have lately had to do a lot of this and a typecase would certainly make for better and easier code.