String input Calculator











up vote
5
down vote

favorite












The following program implements a calculator that takes string inputs in the form of two values separated by an operator. It splits the inputs in to their parts, and then applies the operation, and outputs the result.



As per my understanding I implemented it using Object Oriented patterns.



I am a beginner to Java Programming and OOPs. It would be great help if somebody review the code and provide comments.



SimpleCalculator



package com.main;

import com.calculator.Calculator;
import com.calculator.Division;
import com.factory.CalculatorFactory;
import com.validator.NumberValidator;
import com.validator.OperatorValidator;
import com.validator.Validator;
import com.validator.ZeroValueValidator;

public class Main {
public static void main(String args) {

Validator numberValidator = new NumberValidator();
Validator zeroValidator = new ZeroValueValidator();
Validator operatorValidator = new OperatorValidator();

String inputDatas = {"1234 + 23.45","3444 - 445","97 * 8","999 / 99.2","1234a + 23.45","3444 f445","97 * sd8","999 & 99.2","34 / 0"};


for(String inputs : inputDatas){
String inputArr = inputs.split("\s+");
if(inputArr.length != 3){
System.out.println("Invalid no of parameters, exact 3 accepting");
continue;
}

if(numberValidator.validate(inputArr[0], inputArr[2]) && operatorValidator.validate(inputArr[1])){
Calculator calculator = CalculatorFactory.getCalculator(inputArr[1]);
if(calculator instanceof Division){
if(zeroValidator.validate(inputArr[2])){
System.out.println("Operand2 should not be 0 or 0.0 for Division");
continue;
}
}
double result = calculator.calculate(Double.parseDouble(inputArr[0]), Double.parseDouble(inputArr[2]));
System.out.println(inputs + "=" + result);
} else{
System.out.println("Invalid inputs : "+inputs);
}

}
}
}




package com.calculator;

public interface Calculator {

double calculate(double userInput1, double userInput2);
}




package com.calculator;

public class Addition implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 + userInput2;
}

}




package com.calculator;

public class Substraction implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 - userInput2;
}

}




package com.calculator;

public class Multiplication implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 * userInput2;
}

}




package com.calculator;

public class Division implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1/userInput2;
}

}




package com.validator;

public interface Validator {
boolean validate(String ... dataToBeValidate);
}




package com.validator;

import java.util.regex.Pattern;

public class NumberValidator implements Validator{

@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(!(Pattern.matches("\d+", data) || Pattern.matches("\d+.?\d+", data))){
return false;
}
}
return true;
}


}




package com.validator;

import java.math.BigDecimal;

public class ZeroValueValidator implements Validator{

@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(BigDecimal.ZERO.compareTo(new BigDecimal(data)) != 0){
return false;
}
}
return true;
}

}




package com.validator;

import java.util.Arrays;
import java.util.List;

public class OperatorValidator implements Validator{

List<String> operatorList = Arrays.asList("+","-","/","*");
@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(!operatorList.contains(data.trim())){
return false;
}
}
return true;
}
}




package com.factory;

import com.calculator.Addition;
import com.calculator.Calculator;
import com.calculator.Division;
import com.calculator.Multiplication;
import com.calculator.Substraction;

public class CalculatorFactory {
private static final Addition addition = new Addition();
private static final Substraction substraction = new Substraction();
private static final Multiplication multiplication = new Multiplication();
private static final Division division = new Division();
public static Calculator getCalculator(String operator){
switch (operator) {
case "+": return addition;
case "-": return substraction;
case "*": return multiplication;
case "/": return division;
}
return null;
}
}









share|improve this question
























  • Great first post to Code Review. Can you add in the CalculatorFactory implementation as well? That would be an interesting class to see.
    – rolfl
    May 6 at 13:58










  • Thanks for reminder, i forgot to add. Please look the code again.
    – Akhilesh Singh
    May 6 at 15:38















up vote
5
down vote

favorite












The following program implements a calculator that takes string inputs in the form of two values separated by an operator. It splits the inputs in to their parts, and then applies the operation, and outputs the result.



As per my understanding I implemented it using Object Oriented patterns.



I am a beginner to Java Programming and OOPs. It would be great help if somebody review the code and provide comments.



SimpleCalculator



package com.main;

import com.calculator.Calculator;
import com.calculator.Division;
import com.factory.CalculatorFactory;
import com.validator.NumberValidator;
import com.validator.OperatorValidator;
import com.validator.Validator;
import com.validator.ZeroValueValidator;

public class Main {
public static void main(String args) {

Validator numberValidator = new NumberValidator();
Validator zeroValidator = new ZeroValueValidator();
Validator operatorValidator = new OperatorValidator();

String inputDatas = {"1234 + 23.45","3444 - 445","97 * 8","999 / 99.2","1234a + 23.45","3444 f445","97 * sd8","999 & 99.2","34 / 0"};


for(String inputs : inputDatas){
String inputArr = inputs.split("\s+");
if(inputArr.length != 3){
System.out.println("Invalid no of parameters, exact 3 accepting");
continue;
}

if(numberValidator.validate(inputArr[0], inputArr[2]) && operatorValidator.validate(inputArr[1])){
Calculator calculator = CalculatorFactory.getCalculator(inputArr[1]);
if(calculator instanceof Division){
if(zeroValidator.validate(inputArr[2])){
System.out.println("Operand2 should not be 0 or 0.0 for Division");
continue;
}
}
double result = calculator.calculate(Double.parseDouble(inputArr[0]), Double.parseDouble(inputArr[2]));
System.out.println(inputs + "=" + result);
} else{
System.out.println("Invalid inputs : "+inputs);
}

}
}
}




package com.calculator;

public interface Calculator {

double calculate(double userInput1, double userInput2);
}




package com.calculator;

public class Addition implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 + userInput2;
}

}




package com.calculator;

public class Substraction implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 - userInput2;
}

}




package com.calculator;

public class Multiplication implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 * userInput2;
}

}




package com.calculator;

public class Division implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1/userInput2;
}

}




package com.validator;

public interface Validator {
boolean validate(String ... dataToBeValidate);
}




package com.validator;

import java.util.regex.Pattern;

public class NumberValidator implements Validator{

@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(!(Pattern.matches("\d+", data) || Pattern.matches("\d+.?\d+", data))){
return false;
}
}
return true;
}


}




package com.validator;

import java.math.BigDecimal;

public class ZeroValueValidator implements Validator{

@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(BigDecimal.ZERO.compareTo(new BigDecimal(data)) != 0){
return false;
}
}
return true;
}

}




package com.validator;

import java.util.Arrays;
import java.util.List;

public class OperatorValidator implements Validator{

List<String> operatorList = Arrays.asList("+","-","/","*");
@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(!operatorList.contains(data.trim())){
return false;
}
}
return true;
}
}




package com.factory;

import com.calculator.Addition;
import com.calculator.Calculator;
import com.calculator.Division;
import com.calculator.Multiplication;
import com.calculator.Substraction;

public class CalculatorFactory {
private static final Addition addition = new Addition();
private static final Substraction substraction = new Substraction();
private static final Multiplication multiplication = new Multiplication();
private static final Division division = new Division();
public static Calculator getCalculator(String operator){
switch (operator) {
case "+": return addition;
case "-": return substraction;
case "*": return multiplication;
case "/": return division;
}
return null;
}
}









share|improve this question
























  • Great first post to Code Review. Can you add in the CalculatorFactory implementation as well? That would be an interesting class to see.
    – rolfl
    May 6 at 13:58










  • Thanks for reminder, i forgot to add. Please look the code again.
    – Akhilesh Singh
    May 6 at 15:38













up vote
5
down vote

favorite









up vote
5
down vote

favorite











The following program implements a calculator that takes string inputs in the form of two values separated by an operator. It splits the inputs in to their parts, and then applies the operation, and outputs the result.



As per my understanding I implemented it using Object Oriented patterns.



I am a beginner to Java Programming and OOPs. It would be great help if somebody review the code and provide comments.



SimpleCalculator



package com.main;

import com.calculator.Calculator;
import com.calculator.Division;
import com.factory.CalculatorFactory;
import com.validator.NumberValidator;
import com.validator.OperatorValidator;
import com.validator.Validator;
import com.validator.ZeroValueValidator;

public class Main {
public static void main(String args) {

Validator numberValidator = new NumberValidator();
Validator zeroValidator = new ZeroValueValidator();
Validator operatorValidator = new OperatorValidator();

String inputDatas = {"1234 + 23.45","3444 - 445","97 * 8","999 / 99.2","1234a + 23.45","3444 f445","97 * sd8","999 & 99.2","34 / 0"};


for(String inputs : inputDatas){
String inputArr = inputs.split("\s+");
if(inputArr.length != 3){
System.out.println("Invalid no of parameters, exact 3 accepting");
continue;
}

if(numberValidator.validate(inputArr[0], inputArr[2]) && operatorValidator.validate(inputArr[1])){
Calculator calculator = CalculatorFactory.getCalculator(inputArr[1]);
if(calculator instanceof Division){
if(zeroValidator.validate(inputArr[2])){
System.out.println("Operand2 should not be 0 or 0.0 for Division");
continue;
}
}
double result = calculator.calculate(Double.parseDouble(inputArr[0]), Double.parseDouble(inputArr[2]));
System.out.println(inputs + "=" + result);
} else{
System.out.println("Invalid inputs : "+inputs);
}

}
}
}




package com.calculator;

public interface Calculator {

double calculate(double userInput1, double userInput2);
}




package com.calculator;

public class Addition implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 + userInput2;
}

}




package com.calculator;

public class Substraction implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 - userInput2;
}

}




package com.calculator;

public class Multiplication implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 * userInput2;
}

}




package com.calculator;

public class Division implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1/userInput2;
}

}




package com.validator;

public interface Validator {
boolean validate(String ... dataToBeValidate);
}




package com.validator;

import java.util.regex.Pattern;

public class NumberValidator implements Validator{

@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(!(Pattern.matches("\d+", data) || Pattern.matches("\d+.?\d+", data))){
return false;
}
}
return true;
}


}




package com.validator;

import java.math.BigDecimal;

public class ZeroValueValidator implements Validator{

@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(BigDecimal.ZERO.compareTo(new BigDecimal(data)) != 0){
return false;
}
}
return true;
}

}




package com.validator;

import java.util.Arrays;
import java.util.List;

public class OperatorValidator implements Validator{

List<String> operatorList = Arrays.asList("+","-","/","*");
@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(!operatorList.contains(data.trim())){
return false;
}
}
return true;
}
}




package com.factory;

import com.calculator.Addition;
import com.calculator.Calculator;
import com.calculator.Division;
import com.calculator.Multiplication;
import com.calculator.Substraction;

public class CalculatorFactory {
private static final Addition addition = new Addition();
private static final Substraction substraction = new Substraction();
private static final Multiplication multiplication = new Multiplication();
private static final Division division = new Division();
public static Calculator getCalculator(String operator){
switch (operator) {
case "+": return addition;
case "-": return substraction;
case "*": return multiplication;
case "/": return division;
}
return null;
}
}









share|improve this question















The following program implements a calculator that takes string inputs in the form of two values separated by an operator. It splits the inputs in to their parts, and then applies the operation, and outputs the result.



As per my understanding I implemented it using Object Oriented patterns.



I am a beginner to Java Programming and OOPs. It would be great help if somebody review the code and provide comments.



SimpleCalculator



package com.main;

import com.calculator.Calculator;
import com.calculator.Division;
import com.factory.CalculatorFactory;
import com.validator.NumberValidator;
import com.validator.OperatorValidator;
import com.validator.Validator;
import com.validator.ZeroValueValidator;

public class Main {
public static void main(String args) {

Validator numberValidator = new NumberValidator();
Validator zeroValidator = new ZeroValueValidator();
Validator operatorValidator = new OperatorValidator();

String inputDatas = {"1234 + 23.45","3444 - 445","97 * 8","999 / 99.2","1234a + 23.45","3444 f445","97 * sd8","999 & 99.2","34 / 0"};


for(String inputs : inputDatas){
String inputArr = inputs.split("\s+");
if(inputArr.length != 3){
System.out.println("Invalid no of parameters, exact 3 accepting");
continue;
}

if(numberValidator.validate(inputArr[0], inputArr[2]) && operatorValidator.validate(inputArr[1])){
Calculator calculator = CalculatorFactory.getCalculator(inputArr[1]);
if(calculator instanceof Division){
if(zeroValidator.validate(inputArr[2])){
System.out.println("Operand2 should not be 0 or 0.0 for Division");
continue;
}
}
double result = calculator.calculate(Double.parseDouble(inputArr[0]), Double.parseDouble(inputArr[2]));
System.out.println(inputs + "=" + result);
} else{
System.out.println("Invalid inputs : "+inputs);
}

}
}
}




package com.calculator;

public interface Calculator {

double calculate(double userInput1, double userInput2);
}




package com.calculator;

public class Addition implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 + userInput2;
}

}




package com.calculator;

public class Substraction implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 - userInput2;
}

}




package com.calculator;

public class Multiplication implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1 * userInput2;
}

}




package com.calculator;

public class Division implements Calculator{

@Override
public double calculate(double userInput1, double userInput2) {
return userInput1/userInput2;
}

}




package com.validator;

public interface Validator {
boolean validate(String ... dataToBeValidate);
}




package com.validator;

import java.util.regex.Pattern;

public class NumberValidator implements Validator{

@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(!(Pattern.matches("\d+", data) || Pattern.matches("\d+.?\d+", data))){
return false;
}
}
return true;
}


}




package com.validator;

import java.math.BigDecimal;

public class ZeroValueValidator implements Validator{

@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(BigDecimal.ZERO.compareTo(new BigDecimal(data)) != 0){
return false;
}
}
return true;
}

}




package com.validator;

import java.util.Arrays;
import java.util.List;

public class OperatorValidator implements Validator{

List<String> operatorList = Arrays.asList("+","-","/","*");
@Override
public boolean validate(String... dataToBeValidate){
for(String data : dataToBeValidate){
if(!operatorList.contains(data.trim())){
return false;
}
}
return true;
}
}




package com.factory;

import com.calculator.Addition;
import com.calculator.Calculator;
import com.calculator.Division;
import com.calculator.Multiplication;
import com.calculator.Substraction;

public class CalculatorFactory {
private static final Addition addition = new Addition();
private static final Substraction substraction = new Substraction();
private static final Multiplication multiplication = new Multiplication();
private static final Division division = new Division();
public static Calculator getCalculator(String operator){
switch (operator) {
case "+": return addition;
case "-": return substraction;
case "*": return multiplication;
case "/": return division;
}
return null;
}
}






java beginner object-oriented calculator






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited May 6 at 15:36

























asked May 6 at 11:16









Akhilesh Singh

263




263












  • Great first post to Code Review. Can you add in the CalculatorFactory implementation as well? That would be an interesting class to see.
    – rolfl
    May 6 at 13:58










  • Thanks for reminder, i forgot to add. Please look the code again.
    – Akhilesh Singh
    May 6 at 15:38


















  • Great first post to Code Review. Can you add in the CalculatorFactory implementation as well? That would be an interesting class to see.
    – rolfl
    May 6 at 13:58










  • Thanks for reminder, i forgot to add. Please look the code again.
    – Akhilesh Singh
    May 6 at 15:38
















Great first post to Code Review. Can you add in the CalculatorFactory implementation as well? That would be an interesting class to see.
– rolfl
May 6 at 13:58




Great first post to Code Review. Can you add in the CalculatorFactory implementation as well? That would be an interesting class to see.
– rolfl
May 6 at 13:58












Thanks for reminder, i forgot to add. Please look the code again.
– Akhilesh Singh
May 6 at 15:38




Thanks for reminder, i forgot to add. Please look the code again.
– Akhilesh Singh
May 6 at 15:38










2 Answers
2






active

oldest

votes

















up vote
6
down vote













My first impression was: wow, great test cases. This is something I often miss when looking at beginner's code. Your tests cover each line of code, except for one in the CalculatorFactory:



return null;


I had expected that the test case "999 & 99.2" would test this line, but I was wrong. This looked strange to me, and indeed you have some code duplication:



// OperatorValidator
List<String> operatorList = Arrays.asList("+","-","/","*");

// CalculatorFactory
switch (operator) {
case "+": return addition;
case "-": return substraction;
case "*": return multiplication;
case "/": return division;
}


Because of this duplication, you have to adjust two places whenever you add a new operator to the calculator. This can be done better. The OperatorValidator could simply check CalculatorFactory.getCalculator(operator) == null. Then you can delete the operatorList.



After this change, your code has 100% test coverage, which is impressing. And not only this, the tests also cover many interesting cases from the application domain, which is even more important.



Package names



You have chosen com.calculator and com.validator as package names. You should not use these names unless you own the DNS domains calculator.com and validator.com. Choosing good package names becomes important when you write software that others want to reuse. It's just too easy that someone else has also chosen the name com.calculator, therefore you should pick a more unique package name, maybe based on your name, or the name of your school, company or university.



Class names



I don't like the name Calculator that you gave your interface. A calculator can usually perform several different operations, like addition, subtraction, division. Your interface has only a single method. It therefore should be called Operation. And if you want to be very precise, it is a BinaryOperation since it takes two arguments. Contrast this with SquareRoot, which would be a UnaryOperation.



Substraction should be Subtraction (substract is obsolete).



Variable names





  • dataToBeValidate should be dataToBeValidated, with a d at the end


  • userInput1 and userInput2 don't necessarily come from the user


Input validation



You did a great job here. I tried to trick your calculator of performing a division by zero when I entered 123 / 0e0, but your validation in ZeroValueValidator caught this attempt.



The regular expression d+.?d+ in the NumberValidator is wrong, though. It accepts 123x456 because the . stands for an arbitrary character. You have two choices here:



Pattern.matches("\d+\.?\d+", data)
Pattern.matches("\d+[.]?\d+", data)


CalculatorFactory



In real life, when you go to a factory and ask for something, you always get a new thing. This is not the case for your CalculatorFactory. Therefore it should rather be called OperatorRegistry.



Source code layout



In a few places, your code layout is inconsistent. The indentation is sometimes 3 spaces, most often 4 and sometimes 5. With a good IDE, you should not need to do the indentation yourself, just let the IDE do that.




  • Eclipse: Ctrl+Shift+F

  • IntelliJ: Ctrl+Alt+L

  • Visual Studio Code: Shift+Alt+F


Extensibility



You separated the code in nice, small chunks. Adding a new binary operation like "x to the power of y" or "gcd(x, y)" should be very easy now.



A more tricky step is to add support for unary operations:




  • negate

  • square root

  • square

  • reciprocal

  • natural logarithm

  • base-10 logarithm

  • factorial


This will require much more work for the first operator. But since you modeled the binary operators so well, it should be easy once you have the first operator working.



Automated testing



Currently when you run the tests you have to inspect the output manually to see whether the results are what you expect. You should rewrite the testing code so that all the good cases don't produce any output. That's much easier to check manually.



The next step in this direction is to write unit tests, for example with JUnit.



Summary



You wrote a very good piece of code. It can still be improved in some places, but it works well, handles the corner cases and has good test data. I could criticize some more things, but I think it's enough for now. After you have improved your code, you are welcome to post a follow-up question here on this site.






share|improve this answer






























    up vote
    4
    down vote













    I have a few comments to say about the main() method. It is doing the following:




    1. define the test data

    2. define all possible validators and initialize all of them upfront.

    3. validate the input by applying validators

    4. it knows when a validator should be applied

    5. somewhere inside the validation code block, the method decides that it is now time to do the calculation

    6. the method is also responsible for producing all the output (from validation as well as from calculation)


    so we see that the method is responsible for quite a lot of processing. This has several issues, the most obvious one is breach of the Single responsibility principle. There are other issues here that need addressing:




    1. this calculator is not very useful for the user since it cannot take any user input. test data, as well as "real" user input, should come as run time input. this may be from the command line, from an API call or from some kind of UI. havong the input specified at run time will allow you to modify the test data without need for recompile.


    2. if ZeroValueValidator is applicable only for division, then perhaps the operator needs to determine which Validators to apply? another approach can be that each Validator can say whether it is applicable for a given calculation? so I believe the order of validation procesing should be: first parse the String expression into an Expression instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.


    3. there should be a clear separation between the validation and calculation steps. This will make things clearer and also allow for modifications to one step without affecting the other.


    4. you should consider having the validators throw a custom exception. this is a perfect fit for that mechanism. Java even have some predefined exceptions that you cen reuse like IllegalArgumentException and ArithmeticException. the benefit of throwing java predefined exceptions is that they already have documentation and users of the calculator will be able to know what went wrong.


    5. other areas for separation of concerns can be output production, validator instantiation, etc.







    share|improve this answer





















      Your Answer





      StackExchange.ifUsing("editor", function () {
      return StackExchange.using("mathjaxEditing", function () {
      StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
      StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
      });
      });
      }, "mathjax-editing");

      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "196"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193776%2fstring-input-calculator%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      2 Answers
      2






      active

      oldest

      votes








      2 Answers
      2






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes








      up vote
      6
      down vote













      My first impression was: wow, great test cases. This is something I often miss when looking at beginner's code. Your tests cover each line of code, except for one in the CalculatorFactory:



      return null;


      I had expected that the test case "999 & 99.2" would test this line, but I was wrong. This looked strange to me, and indeed you have some code duplication:



      // OperatorValidator
      List<String> operatorList = Arrays.asList("+","-","/","*");

      // CalculatorFactory
      switch (operator) {
      case "+": return addition;
      case "-": return substraction;
      case "*": return multiplication;
      case "/": return division;
      }


      Because of this duplication, you have to adjust two places whenever you add a new operator to the calculator. This can be done better. The OperatorValidator could simply check CalculatorFactory.getCalculator(operator) == null. Then you can delete the operatorList.



      After this change, your code has 100% test coverage, which is impressing. And not only this, the tests also cover many interesting cases from the application domain, which is even more important.



      Package names



      You have chosen com.calculator and com.validator as package names. You should not use these names unless you own the DNS domains calculator.com and validator.com. Choosing good package names becomes important when you write software that others want to reuse. It's just too easy that someone else has also chosen the name com.calculator, therefore you should pick a more unique package name, maybe based on your name, or the name of your school, company or university.



      Class names



      I don't like the name Calculator that you gave your interface. A calculator can usually perform several different operations, like addition, subtraction, division. Your interface has only a single method. It therefore should be called Operation. And if you want to be very precise, it is a BinaryOperation since it takes two arguments. Contrast this with SquareRoot, which would be a UnaryOperation.



      Substraction should be Subtraction (substract is obsolete).



      Variable names





      • dataToBeValidate should be dataToBeValidated, with a d at the end


      • userInput1 and userInput2 don't necessarily come from the user


      Input validation



      You did a great job here. I tried to trick your calculator of performing a division by zero when I entered 123 / 0e0, but your validation in ZeroValueValidator caught this attempt.



      The regular expression d+.?d+ in the NumberValidator is wrong, though. It accepts 123x456 because the . stands for an arbitrary character. You have two choices here:



      Pattern.matches("\d+\.?\d+", data)
      Pattern.matches("\d+[.]?\d+", data)


      CalculatorFactory



      In real life, when you go to a factory and ask for something, you always get a new thing. This is not the case for your CalculatorFactory. Therefore it should rather be called OperatorRegistry.



      Source code layout



      In a few places, your code layout is inconsistent. The indentation is sometimes 3 spaces, most often 4 and sometimes 5. With a good IDE, you should not need to do the indentation yourself, just let the IDE do that.




      • Eclipse: Ctrl+Shift+F

      • IntelliJ: Ctrl+Alt+L

      • Visual Studio Code: Shift+Alt+F


      Extensibility



      You separated the code in nice, small chunks. Adding a new binary operation like "x to the power of y" or "gcd(x, y)" should be very easy now.



      A more tricky step is to add support for unary operations:




      • negate

      • square root

      • square

      • reciprocal

      • natural logarithm

      • base-10 logarithm

      • factorial


      This will require much more work for the first operator. But since you modeled the binary operators so well, it should be easy once you have the first operator working.



      Automated testing



      Currently when you run the tests you have to inspect the output manually to see whether the results are what you expect. You should rewrite the testing code so that all the good cases don't produce any output. That's much easier to check manually.



      The next step in this direction is to write unit tests, for example with JUnit.



      Summary



      You wrote a very good piece of code. It can still be improved in some places, but it works well, handles the corner cases and has good test data. I could criticize some more things, but I think it's enough for now. After you have improved your code, you are welcome to post a follow-up question here on this site.






      share|improve this answer



























        up vote
        6
        down vote













        My first impression was: wow, great test cases. This is something I often miss when looking at beginner's code. Your tests cover each line of code, except for one in the CalculatorFactory:



        return null;


        I had expected that the test case "999 & 99.2" would test this line, but I was wrong. This looked strange to me, and indeed you have some code duplication:



        // OperatorValidator
        List<String> operatorList = Arrays.asList("+","-","/","*");

        // CalculatorFactory
        switch (operator) {
        case "+": return addition;
        case "-": return substraction;
        case "*": return multiplication;
        case "/": return division;
        }


        Because of this duplication, you have to adjust two places whenever you add a new operator to the calculator. This can be done better. The OperatorValidator could simply check CalculatorFactory.getCalculator(operator) == null. Then you can delete the operatorList.



        After this change, your code has 100% test coverage, which is impressing. And not only this, the tests also cover many interesting cases from the application domain, which is even more important.



        Package names



        You have chosen com.calculator and com.validator as package names. You should not use these names unless you own the DNS domains calculator.com and validator.com. Choosing good package names becomes important when you write software that others want to reuse. It's just too easy that someone else has also chosen the name com.calculator, therefore you should pick a more unique package name, maybe based on your name, or the name of your school, company or university.



        Class names



        I don't like the name Calculator that you gave your interface. A calculator can usually perform several different operations, like addition, subtraction, division. Your interface has only a single method. It therefore should be called Operation. And if you want to be very precise, it is a BinaryOperation since it takes two arguments. Contrast this with SquareRoot, which would be a UnaryOperation.



        Substraction should be Subtraction (substract is obsolete).



        Variable names





        • dataToBeValidate should be dataToBeValidated, with a d at the end


        • userInput1 and userInput2 don't necessarily come from the user


        Input validation



        You did a great job here. I tried to trick your calculator of performing a division by zero when I entered 123 / 0e0, but your validation in ZeroValueValidator caught this attempt.



        The regular expression d+.?d+ in the NumberValidator is wrong, though. It accepts 123x456 because the . stands for an arbitrary character. You have two choices here:



        Pattern.matches("\d+\.?\d+", data)
        Pattern.matches("\d+[.]?\d+", data)


        CalculatorFactory



        In real life, when you go to a factory and ask for something, you always get a new thing. This is not the case for your CalculatorFactory. Therefore it should rather be called OperatorRegistry.



        Source code layout



        In a few places, your code layout is inconsistent. The indentation is sometimes 3 spaces, most often 4 and sometimes 5. With a good IDE, you should not need to do the indentation yourself, just let the IDE do that.




        • Eclipse: Ctrl+Shift+F

        • IntelliJ: Ctrl+Alt+L

        • Visual Studio Code: Shift+Alt+F


        Extensibility



        You separated the code in nice, small chunks. Adding a new binary operation like "x to the power of y" or "gcd(x, y)" should be very easy now.



        A more tricky step is to add support for unary operations:




        • negate

        • square root

        • square

        • reciprocal

        • natural logarithm

        • base-10 logarithm

        • factorial


        This will require much more work for the first operator. But since you modeled the binary operators so well, it should be easy once you have the first operator working.



        Automated testing



        Currently when you run the tests you have to inspect the output manually to see whether the results are what you expect. You should rewrite the testing code so that all the good cases don't produce any output. That's much easier to check manually.



        The next step in this direction is to write unit tests, for example with JUnit.



        Summary



        You wrote a very good piece of code. It can still be improved in some places, but it works well, handles the corner cases and has good test data. I could criticize some more things, but I think it's enough for now. After you have improved your code, you are welcome to post a follow-up question here on this site.






        share|improve this answer

























          up vote
          6
          down vote










          up vote
          6
          down vote









          My first impression was: wow, great test cases. This is something I often miss when looking at beginner's code. Your tests cover each line of code, except for one in the CalculatorFactory:



          return null;


          I had expected that the test case "999 & 99.2" would test this line, but I was wrong. This looked strange to me, and indeed you have some code duplication:



          // OperatorValidator
          List<String> operatorList = Arrays.asList("+","-","/","*");

          // CalculatorFactory
          switch (operator) {
          case "+": return addition;
          case "-": return substraction;
          case "*": return multiplication;
          case "/": return division;
          }


          Because of this duplication, you have to adjust two places whenever you add a new operator to the calculator. This can be done better. The OperatorValidator could simply check CalculatorFactory.getCalculator(operator) == null. Then you can delete the operatorList.



          After this change, your code has 100% test coverage, which is impressing. And not only this, the tests also cover many interesting cases from the application domain, which is even more important.



          Package names



          You have chosen com.calculator and com.validator as package names. You should not use these names unless you own the DNS domains calculator.com and validator.com. Choosing good package names becomes important when you write software that others want to reuse. It's just too easy that someone else has also chosen the name com.calculator, therefore you should pick a more unique package name, maybe based on your name, or the name of your school, company or university.



          Class names



          I don't like the name Calculator that you gave your interface. A calculator can usually perform several different operations, like addition, subtraction, division. Your interface has only a single method. It therefore should be called Operation. And if you want to be very precise, it is a BinaryOperation since it takes two arguments. Contrast this with SquareRoot, which would be a UnaryOperation.



          Substraction should be Subtraction (substract is obsolete).



          Variable names





          • dataToBeValidate should be dataToBeValidated, with a d at the end


          • userInput1 and userInput2 don't necessarily come from the user


          Input validation



          You did a great job here. I tried to trick your calculator of performing a division by zero when I entered 123 / 0e0, but your validation in ZeroValueValidator caught this attempt.



          The regular expression d+.?d+ in the NumberValidator is wrong, though. It accepts 123x456 because the . stands for an arbitrary character. You have two choices here:



          Pattern.matches("\d+\.?\d+", data)
          Pattern.matches("\d+[.]?\d+", data)


          CalculatorFactory



          In real life, when you go to a factory and ask for something, you always get a new thing. This is not the case for your CalculatorFactory. Therefore it should rather be called OperatorRegistry.



          Source code layout



          In a few places, your code layout is inconsistent. The indentation is sometimes 3 spaces, most often 4 and sometimes 5. With a good IDE, you should not need to do the indentation yourself, just let the IDE do that.




          • Eclipse: Ctrl+Shift+F

          • IntelliJ: Ctrl+Alt+L

          • Visual Studio Code: Shift+Alt+F


          Extensibility



          You separated the code in nice, small chunks. Adding a new binary operation like "x to the power of y" or "gcd(x, y)" should be very easy now.



          A more tricky step is to add support for unary operations:




          • negate

          • square root

          • square

          • reciprocal

          • natural logarithm

          • base-10 logarithm

          • factorial


          This will require much more work for the first operator. But since you modeled the binary operators so well, it should be easy once you have the first operator working.



          Automated testing



          Currently when you run the tests you have to inspect the output manually to see whether the results are what you expect. You should rewrite the testing code so that all the good cases don't produce any output. That's much easier to check manually.



          The next step in this direction is to write unit tests, for example with JUnit.



          Summary



          You wrote a very good piece of code. It can still be improved in some places, but it works well, handles the corner cases and has good test data. I could criticize some more things, but I think it's enough for now. After you have improved your code, you are welcome to post a follow-up question here on this site.






          share|improve this answer














          My first impression was: wow, great test cases. This is something I often miss when looking at beginner's code. Your tests cover each line of code, except for one in the CalculatorFactory:



          return null;


          I had expected that the test case "999 & 99.2" would test this line, but I was wrong. This looked strange to me, and indeed you have some code duplication:



          // OperatorValidator
          List<String> operatorList = Arrays.asList("+","-","/","*");

          // CalculatorFactory
          switch (operator) {
          case "+": return addition;
          case "-": return substraction;
          case "*": return multiplication;
          case "/": return division;
          }


          Because of this duplication, you have to adjust two places whenever you add a new operator to the calculator. This can be done better. The OperatorValidator could simply check CalculatorFactory.getCalculator(operator) == null. Then you can delete the operatorList.



          After this change, your code has 100% test coverage, which is impressing. And not only this, the tests also cover many interesting cases from the application domain, which is even more important.



          Package names



          You have chosen com.calculator and com.validator as package names. You should not use these names unless you own the DNS domains calculator.com and validator.com. Choosing good package names becomes important when you write software that others want to reuse. It's just too easy that someone else has also chosen the name com.calculator, therefore you should pick a more unique package name, maybe based on your name, or the name of your school, company or university.



          Class names



          I don't like the name Calculator that you gave your interface. A calculator can usually perform several different operations, like addition, subtraction, division. Your interface has only a single method. It therefore should be called Operation. And if you want to be very precise, it is a BinaryOperation since it takes two arguments. Contrast this with SquareRoot, which would be a UnaryOperation.



          Substraction should be Subtraction (substract is obsolete).



          Variable names





          • dataToBeValidate should be dataToBeValidated, with a d at the end


          • userInput1 and userInput2 don't necessarily come from the user


          Input validation



          You did a great job here. I tried to trick your calculator of performing a division by zero when I entered 123 / 0e0, but your validation in ZeroValueValidator caught this attempt.



          The regular expression d+.?d+ in the NumberValidator is wrong, though. It accepts 123x456 because the . stands for an arbitrary character. You have two choices here:



          Pattern.matches("\d+\.?\d+", data)
          Pattern.matches("\d+[.]?\d+", data)


          CalculatorFactory



          In real life, when you go to a factory and ask for something, you always get a new thing. This is not the case for your CalculatorFactory. Therefore it should rather be called OperatorRegistry.



          Source code layout



          In a few places, your code layout is inconsistent. The indentation is sometimes 3 spaces, most often 4 and sometimes 5. With a good IDE, you should not need to do the indentation yourself, just let the IDE do that.




          • Eclipse: Ctrl+Shift+F

          • IntelliJ: Ctrl+Alt+L

          • Visual Studio Code: Shift+Alt+F


          Extensibility



          You separated the code in nice, small chunks. Adding a new binary operation like "x to the power of y" or "gcd(x, y)" should be very easy now.



          A more tricky step is to add support for unary operations:




          • negate

          • square root

          • square

          • reciprocal

          • natural logarithm

          • base-10 logarithm

          • factorial


          This will require much more work for the first operator. But since you modeled the binary operators so well, it should be easy once you have the first operator working.



          Automated testing



          Currently when you run the tests you have to inspect the output manually to see whether the results are what you expect. You should rewrite the testing code so that all the good cases don't produce any output. That's much easier to check manually.



          The next step in this direction is to write unit tests, for example with JUnit.



          Summary



          You wrote a very good piece of code. It can still be improved in some places, but it works well, handles the corner cases and has good test data. I could criticize some more things, but I think it's enough for now. After you have improved your code, you are welcome to post a follow-up question here on this site.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited 8 hours ago









          mature

          1113




          1113










          answered May 6 at 16:35









          Roland Illig

          10.8k11844




          10.8k11844
























              up vote
              4
              down vote













              I have a few comments to say about the main() method. It is doing the following:




              1. define the test data

              2. define all possible validators and initialize all of them upfront.

              3. validate the input by applying validators

              4. it knows when a validator should be applied

              5. somewhere inside the validation code block, the method decides that it is now time to do the calculation

              6. the method is also responsible for producing all the output (from validation as well as from calculation)


              so we see that the method is responsible for quite a lot of processing. This has several issues, the most obvious one is breach of the Single responsibility principle. There are other issues here that need addressing:




              1. this calculator is not very useful for the user since it cannot take any user input. test data, as well as "real" user input, should come as run time input. this may be from the command line, from an API call or from some kind of UI. havong the input specified at run time will allow you to modify the test data without need for recompile.


              2. if ZeroValueValidator is applicable only for division, then perhaps the operator needs to determine which Validators to apply? another approach can be that each Validator can say whether it is applicable for a given calculation? so I believe the order of validation procesing should be: first parse the String expression into an Expression instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.


              3. there should be a clear separation between the validation and calculation steps. This will make things clearer and also allow for modifications to one step without affecting the other.


              4. you should consider having the validators throw a custom exception. this is a perfect fit for that mechanism. Java even have some predefined exceptions that you cen reuse like IllegalArgumentException and ArithmeticException. the benefit of throwing java predefined exceptions is that they already have documentation and users of the calculator will be able to know what went wrong.


              5. other areas for separation of concerns can be output production, validator instantiation, etc.







              share|improve this answer

























                up vote
                4
                down vote













                I have a few comments to say about the main() method. It is doing the following:




                1. define the test data

                2. define all possible validators and initialize all of them upfront.

                3. validate the input by applying validators

                4. it knows when a validator should be applied

                5. somewhere inside the validation code block, the method decides that it is now time to do the calculation

                6. the method is also responsible for producing all the output (from validation as well as from calculation)


                so we see that the method is responsible for quite a lot of processing. This has several issues, the most obvious one is breach of the Single responsibility principle. There are other issues here that need addressing:




                1. this calculator is not very useful for the user since it cannot take any user input. test data, as well as "real" user input, should come as run time input. this may be from the command line, from an API call or from some kind of UI. havong the input specified at run time will allow you to modify the test data without need for recompile.


                2. if ZeroValueValidator is applicable only for division, then perhaps the operator needs to determine which Validators to apply? another approach can be that each Validator can say whether it is applicable for a given calculation? so I believe the order of validation procesing should be: first parse the String expression into an Expression instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.


                3. there should be a clear separation between the validation and calculation steps. This will make things clearer and also allow for modifications to one step without affecting the other.


                4. you should consider having the validators throw a custom exception. this is a perfect fit for that mechanism. Java even have some predefined exceptions that you cen reuse like IllegalArgumentException and ArithmeticException. the benefit of throwing java predefined exceptions is that they already have documentation and users of the calculator will be able to know what went wrong.


                5. other areas for separation of concerns can be output production, validator instantiation, etc.







                share|improve this answer























                  up vote
                  4
                  down vote










                  up vote
                  4
                  down vote









                  I have a few comments to say about the main() method. It is doing the following:




                  1. define the test data

                  2. define all possible validators and initialize all of them upfront.

                  3. validate the input by applying validators

                  4. it knows when a validator should be applied

                  5. somewhere inside the validation code block, the method decides that it is now time to do the calculation

                  6. the method is also responsible for producing all the output (from validation as well as from calculation)


                  so we see that the method is responsible for quite a lot of processing. This has several issues, the most obvious one is breach of the Single responsibility principle. There are other issues here that need addressing:




                  1. this calculator is not very useful for the user since it cannot take any user input. test data, as well as "real" user input, should come as run time input. this may be from the command line, from an API call or from some kind of UI. havong the input specified at run time will allow you to modify the test data without need for recompile.


                  2. if ZeroValueValidator is applicable only for division, then perhaps the operator needs to determine which Validators to apply? another approach can be that each Validator can say whether it is applicable for a given calculation? so I believe the order of validation procesing should be: first parse the String expression into an Expression instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.


                  3. there should be a clear separation between the validation and calculation steps. This will make things clearer and also allow for modifications to one step without affecting the other.


                  4. you should consider having the validators throw a custom exception. this is a perfect fit for that mechanism. Java even have some predefined exceptions that you cen reuse like IllegalArgumentException and ArithmeticException. the benefit of throwing java predefined exceptions is that they already have documentation and users of the calculator will be able to know what went wrong.


                  5. other areas for separation of concerns can be output production, validator instantiation, etc.







                  share|improve this answer












                  I have a few comments to say about the main() method. It is doing the following:




                  1. define the test data

                  2. define all possible validators and initialize all of them upfront.

                  3. validate the input by applying validators

                  4. it knows when a validator should be applied

                  5. somewhere inside the validation code block, the method decides that it is now time to do the calculation

                  6. the method is also responsible for producing all the output (from validation as well as from calculation)


                  so we see that the method is responsible for quite a lot of processing. This has several issues, the most obvious one is breach of the Single responsibility principle. There are other issues here that need addressing:




                  1. this calculator is not very useful for the user since it cannot take any user input. test data, as well as "real" user input, should come as run time input. this may be from the command line, from an API call or from some kind of UI. havong the input specified at run time will allow you to modify the test data without need for recompile.


                  2. if ZeroValueValidator is applicable only for division, then perhaps the operator needs to determine which Validators to apply? another approach can be that each Validator can say whether it is applicable for a given calculation? so I believe the order of validation procesing should be: first parse the String expression into an Expression instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.


                  3. there should be a clear separation between the validation and calculation steps. This will make things clearer and also allow for modifications to one step without affecting the other.


                  4. you should consider having the validators throw a custom exception. this is a perfect fit for that mechanism. Java even have some predefined exceptions that you cen reuse like IllegalArgumentException and ArithmeticException. the benefit of throwing java predefined exceptions is that they already have documentation and users of the calculator will be able to know what went wrong.


                  5. other areas for separation of concerns can be output production, validator instantiation, etc.








                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered May 7 at 7:47









                  Sharon Ben Asher

                  2,226512




                  2,226512






























                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Code Review Stack Exchange!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      Use MathJax to format equations. MathJax reference.


                      To learn more, see our tips on writing great answers.





                      Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                      Please pay close attention to the following guidance:


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193776%2fstring-input-calculator%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Quarter-circle Tiles

                      build a pushdown automaton that recognizes the reverse language of a given pushdown automaton?

                      Mont Emei