Wednesday, August 15, 2012

Arithmetic Expression Calculator Design and Coding


I have  found interesting problem to redesign want to share with you guys , its open for discussion , i know its not best design we can also use factory pattern for object creation and dependency injection.


Problem Statement:
Java Coding/Design Task

A junior member of your team has been asked to create a simple calculation module (and related tests) that can process arithmetic commands with the input specification:

cmd::= expression* signed_decimal
expresion::= signed_decimal ' '* operator ' '*
operator::= '+' | '-' | '*'
signed_decimal::= '-'? decimal_number
decimal_number::= digits | digits ‘.’ digits
digits::= '0' | non_zero_digit digits*
non_zero_digit::= '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'

He has produced the following. How would you suggest he change it? Please consider the quality of the solution (including the design), and provide an updated version of the code, applying your suggestions.

CalcEnginejava

package Calculator;

/**
 * Basic cal engine for handling +, -, * operations.
 */
public class CalcEngine {

            /**
             * Process a calcuation command string
             * @param p cmd
             * @return
             */
            public String process (String p){
                        String[] stuff = p.split(" ");
                        double result = 0;
                        boolean nextOpAdd = false;
                        boolean nextOpSubtract = false;
                        boolean nextOpMultiply = false;
                        for (String x : stuff) {
                                    try {
                                                double nextVal = Double.parseDouble(x);
                                                if (nextOpAdd)
                                                            result += nextVal;
                                                else if (nextOpSubtract)
                                                            result -= nextVal;
                                                else if (nextOpMultiply)
                                                            result *= nextVal;
                                                else
                                                            result = nextVal;
                                    } catch (Exception e) {
                                                nextOpAdd = false;
                                                nextOpSubtract = false;
                                                nextOpMultiply = false;
                                                if (x.equals("+"))
                                                            nextOpAdd = true;
                                                else if (x.equals("-"))
                                                            nextOpSubtract = true;
                                                else if (x.equals("*"))
                                                            nextOpMultiply = true;
                                    }
                        }
                        return String.valueOf(result);
            }
}


CalcEngineTest.java
import org.junit.*;

package Calculator;
           
public class CalcEngineTest {
                                   
            @Test
            public void Test(){
                        CalcEngine engine = new CalcEngine();
                        System.out.println(engine.process("1 + 2"));
            }
}          







Solution Design:
Grammar
cmd::= expression* signed_decimal
expresion::= signed_decimal ' '* operator ' '*
operator::= '+' | '-' | '*'
signed_decimal::= '-'? decimal_number
decimal_number::= digits | digits ‘.’ digits
digits::= '0' | non_zero_digit digits*
non_zero_digit::= '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'
Assumption:
1) Signed Decimal and Operator separated by spaces. I.e. 1.5 + 2.5 – 3 * 6 + 2 is a valid scenario and it should evaluate to 8.
Based on above understanding of the Given Grammar & from the given code
I have the following observations:
  • All the code lies within one method (Process ()), which may grow unmentionable.
  • One Boolean variable for each operation has been used, Design is not flexible/adaptable for addition of more operators (like divide, square root etc.)
  • Code has operator detection logic handled in Catch block. Actually, catch block shouldn’t be used for input verification purpose.
  • Program State is not being maintained, as all code is within single method.
  • No naming conventions were followed.
  • Use logical and contextual names for local variables
  • Design should be close for modification and open for extension incase in future wants to include ‘/’ then it wont change existing code (Decorator pattern )
  • Validation required
Design:
I am using decorator pattern in following problem
Participant’s classes in the decorator pattern are:
1)  Interface for objects that can have responsibilities added to them dynamically.
2) - Defines an object to which additional responsibilities can be added.
3) Decorator - Maintains a reference to a so object and defines an interface that conforms to Component's interface.
4) Concrete Decorators - Concrete Decorators extend the functionality of the component by adding state or adding behavior.





1)  Main class you can use junit if you want 

package updatedcalculator;

import updatedcalculator.service.CalculatorServiceIfc;
import updatedcalculator.service.CalculatorServiceImpl;
import updatedcalculator.util.Util;


public class CalcEngineTest {

      public static void main(String args[]) {
                  String inputCommand = "1.5 + 2.5 - 3 * 6 + 2 ";
                  //we can use dependency injection for opject creation
                  CalculatorServiceIfc calcEngine = new CalculatorServiceImpl();
                  //is input String valid validation
                  if(!Util.isStringValid(inputCommand)){
                  //Process calculation logic
                  double result = calcEngine.process(inputCommand);
                  System.out.println("==========================================================");
                  System.out.println("Input Command="+inputCommand);
                  System.out.println("Final Output= "+result);
                  System.out.println("==========================================================");
                  }else{
                    System.out.println("Please follow given grammer in requirement doc ,Input command not valid ");
                  }
      }
}

2) I am using multi tier architecture  in my application

package updatedcalculator.service;

public interface CalculatorServiceIfc {
      /**
       *
       */
      public double process(String cammand);

}
package updatedcalculator.service;
import updatedcalculator.bo.CalculatorCommand;
import updatedcalculator.bo.CalculatorCommandIfc;
public class CalculatorServiceImpl implements  CalculatorServiceIfc{

            public double process(String cammand){
                        double calculateValue=0;
                        //We can use dependency injection for creting object
                        CalculatorCommandIfc calcEngine = new CalculatorCommand();
                        calculateValue = calcEngine.process(cammand);
                        return calculateValue;
                       
            }
}

3) Calculation Logic in Bo with decorator pattern
package updatedcalculator.bo;
/**
 * This is contract for CalculatorCommandIfc
 * @author Vaquar Khan
 *
 */
public interface CalculatorCommandIfc {
     

     
      /**
       * Following method return is operator in string valid or not
       * @param operator
       * @return
       */
            public boolean isValidOperator(String operator) ;
            /**
             * Following method is used to return Operation Type
             *
             * @param currentOpertor
             * @return
             */
            public double getOperation(String operaor,double operand1,double operand2);
                 

            /**
             * Following method using for Addition
             *
             * @param op1
             * @param op2
             * @return
             */
            public double addOperation(double operand1, double operand2) ;

            /**
             * Following method using for Subtraction
             *
             * @param op1
             * @param op2
             * @return
             */
            public double substractOperation(double operand1, double operand2);

            /**
             * Following method using for Multiply
             *
             * @param op1
             * @param op2
             * @return
             */
            public double multiplyOperation(double operand1, double operand2);
            // For future extension. Add Handler Method for the new operator here
           
            /**
             *
             */
            public double process(String cammand);
      }


package updatedcalculator.bo;

import updatedcalculator.util.Constants;

public abstract class CalculatorCommandBase implements CalculatorCommandIfc {

      /**
       * Following method return is operator in string valid or not
       * @param operator
       * @return
       */
      public boolean isValidOperator(String operator) {
            return Constants.VALID_OPERTOR.contains(operator);
      }

      /**
       * Following method is used to return Operation Type
       *
       * @param currentOpertor
       * @return
       */
      public abstract double getOperation(String operaor, double operand1,
                  double operand2);

      /**
       * Following method using for Addition
       *
       * @param op1
       * @param op2
       * @return
       */
      public abstract double addOperation(double operand1, double operand2);

      /**
       * Following method using for Subtraction
       *
       * @param op1
       * @param op2
       * @return
       */
      public abstract double substractOperation(double operand1, double operand2);

      /**
       * Following method using for Multiply
       *
       * @param op1
       * @param op2
       * @return
       */
      public abstract double multiplyOperation(double operand1, double operand2);

      /**
       * Process a calcuation command string method process logic for
       * calculation
       *
       * @param cammand
       * @return
       */
      public abstract double process(String cammand);

}

package updatedcalculator.bo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import updatedcalculator.util.Constants;

/**
 * Concrete Componentfor decorator
 * @author Vaquar Khan
 *
 */

public class CalculatorCommand extends CalculatorCommandBase {
           
           
            /**
             * Following method using for Addition
             *
             * @param op1
             * @param op2
             * @return
             */
            public double addOperation(double operand1, double operand2) {
                        return operand1 + operand2;
            }

            /**
             * Following method using for Subtraction
             *
             * @param op1
             * @param op2
             * @return
             */
            public double substractOperation(double operand1, double operand2) {
                        return operand1 - operand2;
            }

            /**
             * Following method using for Multiply
             *
             * @param op1
             * @param op2
             * @return
             */
            public double multiplyOperation(double operand1, double operand2) {
                        return operand1 * operand2;
            }

            /**
             * Following method is used to return Opertaion Type
             *
             * @param currentOpertor
             * @return
             */

            public double getOperation(String operaor, double operand1, double operand2) {
                        double result = 0;

                        if (operaor.equals(Constants.ADD_OPERATION))
                                    //result = operand1 + operand2;
                                    result = this.addOperation(operand1, operand2);
                        else if (operaor.equals(Constants.SUBSTRACT_OPERATION))
                                    //result = operand1 - operand2;
                                    result = this.substractOperation(operand1, operand2);
                        else if (operaor.equals(Constants.MULTIPLY_OPERATION))
                                    //result = operand1 * operand2;
                                    result = this.multiplyOperation(operand1, operand2);

                        return result;
            }

            /**
             * Process a calcuation command string
             * method process logic for calculation
             * @param p cmd
             * @return
             */

            public double process(String cammand) {

                        double operand1 = 0;
                        double operand2 = 0;
                        double result = 0;
                        String operaor = "";

                        boolean isOperatorValidflag = false;
                        boolean isResultemptyFlag = false;
                        List operandList = new ArrayList();
                        List operatorList = new ArrayList();
                        // Split String and created two list
                        String[] stuff = cammand.split(" ");
                        //we can use dependancy injecton for object creation
                        CalculatorCommandIfc calculatorCommand = new CalculatorCommand();
                        //created two list for operator and oprand
                        for (String x : stuff) {
                                    isOperatorValidflag = calculatorCommand.isValidOperator(x);
                                    if (isOperatorValidflag) {
                                                //Operator List
                                                operatorList.add(x);
                                    } else {
                                                //Operand List
                                                operandList.add(x);
                                    }

                        }

                        //Created Iterator
                        Iterator operandItr = operandList.iterator();

                        while (operandItr.hasNext()) {
                                    // get Operator
                                    operaor = (String) operatorList.get(0);
                                    //System.out.println("Operater=" + operaor);

                                    if (!isResultemptyFlag) {
                                                String oper1str = (String) operandList.get(0);
                                                String oper2str = (String) operandList.get(1);
                                                //Pare String to double
                                                operand1 = Double.parseDouble(oper1str);
                                                operand2 = Double.parseDouble(oper2str);
                                                //getOperation
                                                result = this.getOperation(operaor, operand1, operand2);
                                                // remove from list 0 and 1 position
                                                operandList.remove(0);
                                                operandList.remove(0);
                                                //make flag true
                                                isResultemptyFlag = true;
                                    } else {
                                                operand1 = result;
                                                String oprstr = (String) operandList.get(0);
                                                operand2 = Double.parseDouble(oprstr);
                                                //getOperation
                                                result = this.getOperation(operaor, operand1, operand2);
                                                // remove from list
                                                operandList.remove(0);
                                    }// end else
                                    // remove first operator
                                    operatorList.remove(0);
                        }// /end While

                        return result;
            }
}

In future if required any enhancement then decorate object and modify method as per requirement
package updatedcalculator.bo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import updatedcalculator.util.Constants;

/**
 * If got new requirement decorate NewRequirementCalculatorCommand
 * @author Vaquar Khan
 *
 */
public abstract class NewRequirementCalculatorCommand extends CalculatorCommandBase {

           

            /**
             * Following method using for Addition
             *
             * @param op1
             * @param op2
             * @return
             */
            public double addOperation(double operand1, double operand2) {
                        return operand1 + operand2;
            }

            /**
             * Following method using for Subtraction
             *
             * @param op1
             * @param op2
             * @return
             */
            public double substractOperation(double operand1, double operand2) {
                        return operand1 - operand2;
            }

            /**
             * Following method using for Multiply
             *
             * @param op1
             * @param op2
             * @return
             */
            public double multiplyOperation(double operand1, double operand2) {
                        return operand1 * operand2;
            }

            /**
             * Following method is used to return Opertaion Type
             *
             * @param currentOpertor
             * @return
             */

            public double getOperation(String operaor, double operand1, double operand2) {
                        double result = 0;

                        if (operaor.equals(Constants.ADD_OPERATION))
                                    //result = operand1 + operand2;
                                    result = this.addOperation(operand1, operand2);
                        else if (operaor.equals(Constants.SUBSTRACT_OPERATION))
                                    //result = operand1 - operand2;
                                    result = this.substractOperation(operand1, operand2);
                        else if (operaor.equals(Constants.MULTIPLY_OPERATION))
                                    //result = operand1 * operand2;
                                    result = this.multiplyOperation(operand1, operand2);

                        return result;
            }

            /**
             * Process a calcuation command string
             * method process logic for calculation
             * @param p cmd
             * @return
             */

            public double process(String cammand) {

                        double operand1 = 0;
                        double operand2 = 0;
                        double result = 0;
                        String operaor = "";

                        boolean isOperatorValidflag = false;
                        boolean isResultemptyFlag = false;
                        List operandList = new ArrayList();
                        List operatorList = new ArrayList();
                        // Split String and created two list
                        String[] stuff = cammand.split(" ");
                        //we can use dependancy injecton for object creation
                        CalculatorCommandIfc calculatorCommand = new CalculatorCommand();
                        //created two list for operator and oprand
                        for (String x : stuff) {
                                    isOperatorValidflag = calculatorCommand.isValidOperator(x);
                                    if (isOperatorValidflag) {
                                                //Operator List
                                                operatorList.add(x);
                                    } else {
                                                //Operand List
                                                operandList.add(x);
                                    }

                        }

                        //Created Iterator
                        Iterator operandItr = operandList.iterator();

                        while (operandItr.hasNext()) {
                                    // get Operator
                                    operaor = (String) operatorList.get(0);
                                    //System.out.println("Operater=" + operaor);

                                    if (!isResultemptyFlag) {
                                                String oper1str = (String) operandList.get(0);
                                                String oper2str = (String) operandList.get(1);
                                                //Pare String to double
                                                operand1 = Double.parseDouble(oper1str);
                                                operand2 = Double.parseDouble(oper2str);
                                                //getOperation
                                                result = this.getOperation(operaor, operand1, operand2);
                                                // remove from list 0 and 1 position
                                                operandList.remove(0);
                                                operandList.remove(0);
                                                //make flag true
                                                isResultemptyFlag = true;
                                    } else {
                                                operand1 = result;
                                                String oprstr = (String) operandList.get(0);
                                                operand2 = Double.parseDouble(oprstr);
                                                //getOperation
                                                result = this.getOperation(operaor, operand1, operand2);
                                                // remove from list
                                                operandList.remove(0);
                                    }// end else
                                    // remove first operator
                                    operatorList.remove(0);
                        }// /end While

                        return result;
            }
}


4)Util for validation and constants

package updatedcalculator.util;

public interface Constants {
    public String Empty = "";
    public String VALID_OPERTOR = "+-*";
    public String ADD_OPERATION = "+";
    public String SUBSTRACT_OPERATION = "-";
    public String MULTIPLY_OPERATION = "*";
}


package updatedcalculator.util;
/**
 *
 */
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;

public class Util {

    /**
     * Input String Validation
     * @param aText
     * @return
     */
    public static boolean isStringValid(String aText) {

        final StringCharacterIterator iterator = new StringCharacterIterator(
                aText);
        char character = iterator.current();
        boolean valid = false;
        while (character != CharacterIterator.DONE) {
            if (character == '<') {
                valid = true;
            } else if (character == '>') {
                valid = true;
            } else if (character == '&') {
                valid = true;
            } else if (character == '!') {
                valid = true;
            } else if (character == '#') {
                valid = true;
            } else if (character == '$') {
                valid = true;
            } else if (character == '%') {
                valid = true;
            } else if (character == '\'') {
                valid = true;
            } else if (character == '(') {
                valid = true;
            } else if (character == ')') {
                valid = true;
            } else if (character == ',') {
                valid = true;
            }else if (character == '/') {
                valid = true;
            } else if (character == ':') {
                valid = true;
            } else if (character == ';') {
                valid = true;
            } else if (character == '=') {
                valid = true;
            } else if (character == '?') {
                valid = true;
            } else if (character == '@') {
                valid = true;
            } else if (character == '[') {
                valid = true;
            } else if (character == '\\') {
                valid = true;
            } else if (character == ']') {
                valid = true;
            } else if (character == '^') {
                valid = true;
            } else if (character == '_') {
                valid = true;
            } else if (character == '`') {
                valid = true;
            } else if (character == '{') {
                valid = true;
            } else if (character == '|') {
                valid = true;
            } else if (character == '}') {
                valid = true;
            } else if (character == '~') {
                valid = true;
            }

            character = iterator.next();
        }

        return valid;
    }
}


No comments:

Post a Comment