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;
}
}
java beginner object-oriented calculator
add a comment |
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;
}
}
java beginner object-oriented calculator
Great first post to Code Review. Can you add in theCalculatorFactory
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
add a comment |
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;
}
}
java beginner object-oriented calculator
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
java beginner object-oriented calculator
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 theCalculatorFactory
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
add a comment |
Great first post to Code Review. Can you add in theCalculatorFactory
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
add a comment |
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 bedataToBeValidated
, with ad
at the end
userInput1
anduserInput2
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.
add a comment |
up vote
4
down vote
I have a few comments to say about the main()
method. It is doing the following:
- define the test data
- define all possible validators and initialize all of them upfront.
- validate the input by applying validators
- it knows when a validator should be applied
- somewhere inside the validation code block, the method decides that it is now time to do the calculation
- 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:
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.
if
ZeroValueValidator
is applicable only for division, then perhaps the operator needs to determine whichValidator
s to apply? another approach can be that eachValidator
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 anExpression
instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.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.
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
andArithmeticException
. 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.other areas for separation of concerns can be output production, validator instantiation, etc.
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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 bedataToBeValidated
, with ad
at the end
userInput1
anduserInput2
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.
add a comment |
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 bedataToBeValidated
, with ad
at the end
userInput1
anduserInput2
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.
add a comment |
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 bedataToBeValidated
, with ad
at the end
userInput1
anduserInput2
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.
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 bedataToBeValidated
, with ad
at the end
userInput1
anduserInput2
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.
edited 8 hours ago
mature
1113
1113
answered May 6 at 16:35
Roland Illig
10.8k11844
10.8k11844
add a comment |
add a comment |
up vote
4
down vote
I have a few comments to say about the main()
method. It is doing the following:
- define the test data
- define all possible validators and initialize all of them upfront.
- validate the input by applying validators
- it knows when a validator should be applied
- somewhere inside the validation code block, the method decides that it is now time to do the calculation
- 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:
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.
if
ZeroValueValidator
is applicable only for division, then perhaps the operator needs to determine whichValidator
s to apply? another approach can be that eachValidator
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 anExpression
instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.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.
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
andArithmeticException
. 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.other areas for separation of concerns can be output production, validator instantiation, etc.
add a comment |
up vote
4
down vote
I have a few comments to say about the main()
method. It is doing the following:
- define the test data
- define all possible validators and initialize all of them upfront.
- validate the input by applying validators
- it knows when a validator should be applied
- somewhere inside the validation code block, the method decides that it is now time to do the calculation
- 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:
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.
if
ZeroValueValidator
is applicable only for division, then perhaps the operator needs to determine whichValidator
s to apply? another approach can be that eachValidator
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 anExpression
instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.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.
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
andArithmeticException
. 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.other areas for separation of concerns can be output production, validator instantiation, etc.
add a comment |
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:
- define the test data
- define all possible validators and initialize all of them upfront.
- validate the input by applying validators
- it knows when a validator should be applied
- somewhere inside the validation code block, the method decides that it is now time to do the calculation
- 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:
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.
if
ZeroValueValidator
is applicable only for division, then perhaps the operator needs to determine whichValidator
s to apply? another approach can be that eachValidator
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 anExpression
instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.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.
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
andArithmeticException
. 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.other areas for separation of concerns can be output production, validator instantiation, etc.
I have a few comments to say about the main()
method. It is doing the following:
- define the test data
- define all possible validators and initialize all of them upfront.
- validate the input by applying validators
- it knows when a validator should be applied
- somewhere inside the validation code block, the method decides that it is now time to do the calculation
- 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:
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.
if
ZeroValueValidator
is applicable only for division, then perhaps the operator needs to determine whichValidator
s to apply? another approach can be that eachValidator
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 anExpression
instance that holds operand/s and operator/s (that can perhaps be defined as enum?). then apply further "mathematical" validation.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.
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
andArithmeticException
. 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.other areas for separation of concerns can be output production, validator instantiation, etc.
answered May 7 at 7:47
Sharon Ben Asher
2,226512
2,226512
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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