Teaser Image

qnoid

Markos Charatzas - Edinburgh, UK




/**
     * Good luck with that. Especially when it grows from a scary one to a mean one.
     */
    public class ScaryCalculator
    {
        /**
         * Hard to maintain. 
         * Need to read through the if(s) and identify the code and variables that
         * you are interested. Not pleasant.  
         * 
         * Hard to extend.
         * Every time you need to add an operation you have to look at that scary
         * method and find a way to stick your code in it without messing with the 
         * rest. Not fun.
         * 
         * Not reusable.
         * You can't reuse any of the operations on a different class. Not efficient.
         */
        public int calculate(int x, int y, String operator)
        {
            if("+".equals(operator)){
                return x + y;
            }
            else if("-".equals(operator)){
                return x - y;
            }
            else if("*".equals(operator)){
                return x * y;
            }
            else if("/".equals(operator)){
                return x / y;
            }
            
            throw new UnsupportedOperationException(operator);
        }
    }


    /**
     * Hello polymorphism!
     */
    public class FunCalculator
    {
        /**
         * Interface to the rescue. Each operation is implemented differently.
         * So depepnding on the operation you pass in, you get a different result!
         * Oh the joy of polymorphism. (Maintenable)
         */
        public int calculate(int x, int y, Operation operation) {
        return operation.make(x, y);
        }
    }


    /**
     * Want to support a new Operation? Implement this interface! (Extensible)
     */
    public interface Operation
    {
        /**
         * Free to do any operation you like
         */
        public int make(int x, int y);
    }

    /**
     * Classes implementing that interface
     */
    public class Addition implements Operation
    {
        @Override
        public int make(int x, int y) {
        return x + y;
        }
    }

    public class Subtraction implements Operation
    {
        @Override
        public int make(int x, int y) {
        return x - y;
        }
    }

    public class Multiplication implements Operation
    {
        @Override
        public int make(int x, int y) {
        return x * y;
        }
    }

    public class Division implements Operation
    {
        @Override
        public int make(int x, int y) {
        return x / y;
        }
    }

    /**
     * Encapsulates the decision on which operation you get based on the operator.
     * Operations for everyone! (Reusable) 
     */
    public class OperationFactory
    {
        /**
         * Use a Map as an index for your operations!
         * Want to extend to include a new one? Simply put it in the Map!
         */
        private final Map<String, Operation> operations = 
            new HashMap<String, Operation>(){
                {
                    this.put("+", new Addition());
                    this.put("-", new Subtraction());
                    this.put("*", new Multiplication());
                    this.put("/", new Division());
                }
            };

        /**
         * @param operator what's your choice?
         */
        public Operation newOperation(String operator) 
        {
            Operation operation = this.operations.get(operator);
            
            /**
             * An innocent if
             */
            if(operation == null)
                throw new UnsupportedOperationException(operator);
            
        return operation;
        }
    }

    /**
     * Let's put it to the test
     */
    public class CalculatorTest
    {
        @Test
        public void make()
        {
            int x = 2;
            int y = 2;
            
            OperationFactory operationFactory = new OperationFactory();
            
            Operation addition = operationFactory.newOperation("+");        
            Operation subtraction = operationFactory.newOperation("-");        
            Operation multiplication = operationFactory.newOperation("*");
            Operation division = operationFactory.newOperation("/");        
            
            FunCalculator calculator = new FunCalculator();
            
            Assert.assertEquals(4, calculator.calculate(x, y, addition));
            Assert.assertEquals(0, calculator.calculate(x, y, subtraction));
            Assert.assertEquals(4, calculator.calculate(x, y, multiplication));        
            Assert.assertEquals(1, calculator.calculate(x, y, division));
        }
    }

If. One of the fundamental keywords in a programming language and most abused (close second is inheritance [Disclaimer]). Has been there since the dawn of programming so people are fond of it. A joy for juniors as it gives them power to put some logic in their code.

Now, there is nothing wrong with if per se. There are some valid cases where an if is what you need but I have come to realise [Disclaimer] that one if per method should be adequate. (A couple are ok, as long as are part of the same branch)

Usually one of the most common wrong usage of an if is failing to identify polymorphism.

Take a look at the ScaryCalculator#calculate(int, int String) method.

There are actually 2 things going on in the code.

  • the decision on which operation to make
  • the different operation based on the String operator.

When you see a code structured like that you have most likely encountered polymorphism. (You are blessed) What this means is that you have objects that the have a common method but implemented differently.

  1. identify what those objects are. Operation(s); that's your interface.
  2. identify the common code. Make the operation; that's your interface method.
  3. identify the different operations that you have. Addition, Subtraction, Multiplication, Division; these are your classes implementing that interface each encapsulating the relative operation code.

Also, use a map for an index if what you actually need is a choice!

There are a couple of other wrong usages of if that will try to explore in the Don't abuse the If series, but please feel free to explore them yourself or comment below.

(Did you notice the if there? ;-))

The Don't lose your train of thoughts series are far from over

Disclaimer: Not based on scientific research but from experience.

Disclaimer 2: This is example code. Not quality code (limited to 2 numbers, String literals instead of enums) but the advice still stands. Take this instead.