Abandoning switch In Three (And A Bit) Steps

Everyone who’s worked in a C-derived language has seen a switch statement or two in their lives. There are a few well-known reasons not to use them, but somehow they crop up now and then anyways because they’re just so convenient — initially.

Switch is a holdover from lower-level languages (mostly, various assembly languages) where the destination of a jmp instruction could be computed or looked up in a table. In modern languages, there are usually more appropriate structures for determining which piece of code to jump to: that’s what virtual dispatch does, for example.

Unfortunately, it’s not always obvious how to “fix” code that makes heavy use of switch.

I normally try to avoid this, but for the sake of clarity I’ve omitted a couple of good style points. Online presentation makes certain Java conventions difficult to present; just because the following examples use a single compilation unit with default-visibility symbols doesn’t mean you should, too.

Starting Points

Let’s take a simple example that uses a switch statement to control the state of some service. The surrounding context involves a simple control protocol for a light dimmer: it can either be turned fully off, turned fully on, or set to a “dim” state halfway between the two.

class States {
    public final int OFF = 0;
    public final int DIM = 1;
    public final int ON = 2;
}
 
class DimmerController {
    public void setIntensity(int intensity) {
        doStuffBeforeChangingIntensity();
        switch (intensity) {
            case OFF:
                disable();
                break;
            case DIM:
                beginDimCycle();
                break;
            case ON:
                beginFullCycle();
                break;
        }
        doStuffAfterChangingIntensity();
    }
 
    private void disable() {
        /* ... */
    }
 
    private void beginDimCycle() {
        /* ... */
    }
 
    private void beginFullCycle() {
        /* ... */
    }
}

Eminent Domain

There are a few obvious problems with the version above. For starters, there’s nothing preventing someone from passing the wrong value, either from the wrong set of constants or by hard-coding the literal values instead of using the constants. We could add a default clause that raises an IllegalArgumentException, but we’d still only find out about the problem at runtime. We’d really like to find out about errors in our programs as early as possible: compile time, or even as we type them. Most languages provide a tool for doing that; since we’re using Java, we’ll use an enum.

enum State {
    OFF,
    DIM,
    ON
}
 
class DimmerController {
    public void setIntensity(State intensity) {
        doStuffBeforeChangingIntensity();
        switch (intensity) {
            case OFF:
                disable();
                break;
            case DIM:
                beginDimCycle();
                break;
            case ON:
                beginFullCycle();
                break;
        }
        doStuffAfterChangingIntensity();
    }
 
    private void disable() {
        /* ... */
    }
 
    private void beginDimCycle() {
        /* ... */
    }
 
    private void beginFullCycle() {
        /* ... */
    }
}

On Your Best Behaviour

That’s better, but it still has most of the fundamental problems switch statements bring with them. While I’m only showing one switch, apps that have them tend to have multiple switches over the same set of constants, which leads to code duplication throughout the app and makes it hard to determine what a given constant will cause the system to do. In an object-oriented system, we normally try to keep behaviour with the data it works with, and as a side effect of refactoring to using an enumerated type, we now have real objects to use. Let’s add some methods to our enumerated type to handle the per-case behaviour.

enum State {
    OFF() {
        public void apply() {
            disable();
        }
 
        private void disable() {
            /* ... */
        }
    },
    DIM() {
        public void apply() {
            beginDimCycle();
        }
 
        private void beginDimCycle() {
            /* ... */
        }
    },
    ON() {
        public void apply() {
            beginFullCycle();
        }
 
        private void beginFullCycle() {
            /* ... */
        }
    };
 
    public abstract void apply();
}
 
class DimmerController {
    public void setIntensity(State intensity) {
        doStuffBeforeChangingIntensity();
        intensity.apply();
        doStuffAfterChangingIntensity();
    }
}

Opening the Set

You can stop here and have a reasonably well-structured program. The specifics of what each constant means are stored with the constant, and it’s now impossible to pass an illegal value into setIntensity. However, we’ve still embedded into DimmerController the assumption that we will only ever be setting the intensity of a three-state light - but we don’t actually use that assumption anywhere. We can refactor further to eliminate this dependency by realizing that State’s public interface really represents any arbitrary dimmer state callback:

 
interface State {
    public void apply();
}
 
enum DimmerState implements State {
    OFF() {
        public void apply() {
            disable();
        }
 
        private void disable() {
            /* ... */
        }
    },
    DIM() {
        public void apply() {
            beginDimCycle();
        }
 
        private void beginDimCycle() {
            /* ... */
        }
    },
    ON() {
        public void apply() {
            beginFullCycle();
        }
 
        private void beginFullCycle() {
            /* ... */
        }
    };
 
    public abstract void apply();
}
 
class DimmerController {
    public void setIntensity(State intensity) {
        doStuffBeforeChangingIntensity();
        intensity.apply();
        doStuffAfterChangingIntensity();
    }
}

Everything In Its Place

On the other hand, sometimes the heavy lifting (in our case, the work of actually changing the intensity of a light) actually belongs outside the enumeration. This is true most of the time: enumerations can’t hold per-use state, and creating a new State instance (from the interface-based example above) for each light isn’t always appropriate. We can pull those methods back out of the enumeration by introducing a third class to act as a callback for the apply method:

enum State {
    OFF() {
        public void applyTo(DimmerCallback dimmerCallback) {
            dimmerCallback.disable();
        }
    },
    DIM() {
        public void applyTo(DimmerCallback dimmerCallback) {
            dimmerCallback.beginDimCycle();
        }
    },
    ON() {
        public void applyTo(DimmerCallback dimmerCallback) {
            dimmerCallback.beginFullCycle();
        }
    };
 
    public abstract void applyTo(DimmerCallback dimmerCallback);
}
 
 
class /* or interface */ DimmerCallback {
    private void disable() {
        /* ... */
    }
 
    private void beginDimCycle() {
        /* ... */
    }
 
    private void beginFullCycle() {
        /* ... */
    }
}
 
class DimmerController {
    // Or from dependency injection, or...
    private final DimmerCallback dimmerCallback = new DimmerCallback();
 
    public void setIntensity(State intensity) {
        doStuffBeforeChangingIntensity();
        intensty.applyTo(dimmerCallback);
        doStuffAfterChangingIntensity();
    }
}

Ok, But I Have This Integer

In most of the cases where people feel compelled to use magic ints or an enumerated type, there’s an external force at work. The most common scenario involves file formats or network protocols where an integer or short known string needs to map directly to program behaviour. With the behaviour separated out into a real type with testable features, you lose the ability to near-blindly pass the protocol value directly into your logic.

Remember when I said that switch statements are really a giant lookup table? That should give you a hint. You can still use a lookup table to convert from an external representation to an internal one; depending on how far down the chain of refactorings you’ve gone, you may even get one for free. Consider the following, assuming we still have a State enumeration:

public State parseRequestedState(int state) throws ProtocolException {
    try {
        return State.values()[state];
    } catch (IndexOutOfBoundsException ioobe) {
        throw new UnexpectedValueException(ioobe);
   }
}

Java’s enumeration support gives us the lookup table we need for free. We can do the same thing with strings, if they happen to match the enum constants’ names; alternately, we can use an explicit lookup table stored in an array or a HashMap. There are also plenty of strategies for automatically populating a lookup table using mechanisms like classpath scanning, ServiceLoader, annotation processing tools, and so on.

There are lots of places to go from here. For example, enum-based implementations of the state object pattern begin with a very similar refactoring. In languages without strong enums, the structure outlined here can be implemented using normal classes with a fixed pool of instances, instead (Java’s enum support does exactly that, under the hood), skipping directly to the interface-based version near the end.

2 Comments

  • By Angelo Genovese, July 23, 2010 @ 3:35 pm

    Nice writeup, there are some similar ones in Martin Fowler’s refactoring book.

  • By Owen, July 23, 2010 @ 6:16 pm

    Yep. None of this is spectacularly novel, admittedly. The motivating force behind this post is having had to explain this series of refactorings to people in Freenode’s ##java three time (and then you automate).

Other Links to this Post

RSS feed for comments on this post. TrackBack URI

Leave a comment

Image | WordPress Themes