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 = "*";
}
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;
}
}
/**
*
*/
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