Tuesday, December 4, 2012

Removing switch statement in object oriented programming

I remember listening on the faculty how 'switch' statements shouldn't be used in OOP at ALL. In someway I though of this as idealistic approach. And now, couple years later, once I've seen design patterns in practice rather than only in theory. By the time I dived further into programming I realized that switch statement can be removed by object inheritance.

To keep examples compact, I'll use Groovy as language for demonstration. If we take classical example from every OOP beginner's book animal, and let's say we want to get the animal's sound. If we take most dummy, and procedural-oriented solution:

class Animal {
   
    def type;
    
    public static String getSound(String type){
        switch(type){
             case 'cat' : return  'Meow'
             case 'dog' : return  'Woof'
             case 'sheep': return 'Blah blah'
             case 'pig'  : return 'Oink'
        }
    }
}

print new Animal(type:'dog').getSound()

Source code is simple to read, so I won't be pasting the output. However, first thing that comes as an idea is putting inheritance to work, and create sub-classes of class Animal for each one of the animals:


class Animal { 
    protected String sound;
    public String getSound(){
       return sound;
    }
}

class Dog extends Animal {
    public Dog() {
        sound = 'Woof';
    }
}

class Cat extends Animal {
    public Cat() {
        sound = 'Meow';
    }
}

class Sheep extends Animal {
    public Sheep() {
        sound = 'Blah';
    }
}

class Pig extends Animal {
    public Pig() {
        sound = 'Oink';
    }
}

print new Dog().getSound()

This looks better, now switch statement. However, you still need an instance of an object to get a sound. So this model does remove switch statement in case model need to satisfy only one question

What is the  sound of this particular animal?
- And also allows us to create mutant animals that do not sound normally



However, it doesn't answer the question:

What is the sound of a cat?

This answer gives us model from first example, where we wanted to remove switch statement in first place.
Model that can answer both questions is the one that allows hastable data structure to decouple data from the code

class Animal { 
    private static def animalTypesToSounds = 
    //we wont be able to use both Animal.getSound('dog') and Animal.getSound('DOG')
     [  'dog' : 'Woof' ,
        'cat': 'Meow' ,
        'sheep' : 'Blah' ,
        'pig' : 'Oink' ] ;
    
    protected String sound;
    
    public String getSound() {
       if(sound) {
           return sound; //still allowing mutant animals
       }
       return animalTypesToSounds[this.class.name.toLowerCase()]
    }
    public static String  getSound(def type){ 
          if(type instanceof Class){
              type = type.name
          }
          return animalTypesToSounds[type.toLowerCase()]
    }
    
}

class Dog extends Animal { 
}

class Cat extends Animal {
}

class Sheep extends Animal {
}

class Pig extends Animal {
}

println new Dog().getSound()
println Animal.getSound(Cat.class)
println Animal.getSound('DOG')

//a mutant cat
println new Cat(sound:'Woof').getSound()

Output:

Woof
Meow
Woof
Woof


Code examples above can be easily applied to any OOP language. I've run onto application of above refactoring method into several places, like responding to command code, or mapping different handlers onto different service response codes.. Sounds like stuff from the faculty is not just theory.