0
\$\begingroup\$

I am doing a practice project to create a calculator with OOP principles. Below is my CalculatorApp that communicates with Calculator(to calc integers) and StringParser(to tokenize input). For CalculatorApp to work, strict rules need to follow. For example, the input must use "add" not "plus" and "(" not any other kind. Therefore I have so many hardcoded variables in the class. Is it a bad design? Am I doing too much in process and is creating variables (i.e wordToSign_map) on the fly bad practice? Should I refactor all the variables to another class? This is for a job so I need to make sure I do my best. Any feedback will be greatly appreciated.

public class CalculatorApp {

    String input;
    Calculator calculator;
    int result;

    final String open_parentheses = "(";
    final String close_parentheses = ")";
    final String delimiter = " ";


    public CalculatorApp(Calculator calculator){
        this.calculator = calculator;
    }

    public CalculatorApp input (String input){
        this.input = input;
        return this;
    }

    public CalculatorApp process (){
        if(!isExpression()){
            result=Integer.parseInt(input);
            return this;
        }

        Map<String, String> wordToSign_map = new HashMap<>();
        wordToSign_map.put("add", "+");
        wordToSign_map.put("multiply", "*");

        Map<String, String> parentheses_spacing_map = new HashMap<>();
        parentheses_spacing_map.put(open_parentheses, open_parentheses+delimiter);
        parentheses_spacing_map.put(close_parentheses, delimiter+close_parentheses);

        StringParser stringParser = new StringParser(input, wordToSign_map, parentheses_spacing_map, delimiter);

        String[] parsedString = stringParser.TokenizeExpression();

        Set<String> operatorSigns = new HashSet<>();
        operatorSigns.addAll(wordToSign_map.values());

        try {
            Tree tree = new Tree();
            tree.constructATree(parsedString, operatorSigns, open_parentheses, close_parentheses);
            result = evaluateExpressionTree(tree.getRoot());
        } catch (EmptyStackException e) {
            System.out.println("Invalid input");
            throw e;
        }
        return this;
    }

    public int output (){
        return result;
    }

    private boolean isExpression(){
        return input.contains(open_parentheses) && input.contains(close_parentheses);
    }
}

This is my Tree class. It takes a string list and creates nodes and children around the brackets and +,* signs. That's why the string has to be exactly one-spaced and can't contain any unknown signs. Should I leave the comments there? Is it too messy and hard to read? Is passing too many args in the method call bad, should I instead pass them in the constructor and hold them as fields?

    public class Tree {

    Node root;

    public Node getRoot() {
        return root;
    }

    public void constructATree (String[] parsedString, Set<String> operatorSigns, String startExp, String endExp) throws EmptyStackException {
        Stack<Node> localRoot = new Stack<>();
        for (int i = 0; i < parsedString.length; i++) {
            if(parsedString[i].equals(startExp)) { // ignore (
                continue;
            }
            if(operatorSigns.contains(parsedString[i])){ // operator for expression
                localRoot.push(new Node(parsedString[i], true));

            } else if(parsedString[i].equals(endExp)) // end of an expression
            {
                Node tempNode = localRoot.pop(); // get the last node
                if(localRoot.empty()){
                    this.root = tempNode; // if there is only one expression return it as the root of the tree
                } else {
                    Node tail = localRoot.peek(); //
                    tail.addChild(tempNode); // add the children from last node to trailing node
                }
            } else {
                Node tempNode = localRoot.pop();
                tempNode.addChild(new Node(parsedString[i], false)); // add values to children of operator node
                localRoot.push(tempNode);
            }
        }
    }
}
\$\endgroup\$
3
  • 3
    \$\begingroup\$ To comply with the rules of this site, please, change the title so it describes what your code is doing. Most classes interact with other classes, the current title is of very little value. \$\endgroup\$
    – slepic
    Commented Jan 3, 2020 at 7:49
  • 1
    \$\begingroup\$ Shall we guess about Node? \$\endgroup\$
    – greybeard
    Commented Jan 3, 2020 at 9:18
  • \$\begingroup\$ @greybeard Node class is a simple object that holds data(string), isOperator(boolean) and a list of children nodes. It has getters and setters methods for the fields \$\endgroup\$
    – azlan_909
    Commented Jan 3, 2020 at 18:08

1 Answer 1

1
\$\begingroup\$
app.input(input);
app.process();
var result = app.output();

This is a bad practice. Every function/method has means of passing input, it has body and it has return value. Setting the input to class field, then calling method without arguments to process the set input, then storing result to another class field, then calling another method to retrive the result is utterly complicated, confusing and error prone.

You can forget to call input and call process directly which will fail or process the previous input.

You can forget to call process and retrieve an unexpected null result or retrieve the result of previous process.

You can have momory occupied longer then necesary even if you don't care for the result anymore.

Why not use all the channels of a function?

var result = app.process(input);

Now imagine the result has been printed for example and it is no longer needed. In your implementation the memory is still siezed (because app object holds reference to it) until the app object is destroyed. In my implementation the momory is released (once the scope of the variable is left) regardless of the app object being destroyed or not.

Returning this

Be very thouthful when returning this. You hardly ever need to return this. It only leads to thoughts about saving outputs to the class fields, because it feels like return value is already siezed by this and thus the real result must be provided through a different channel. But true is that this is always available to the caller (because they know the reference to the object) and so if the return value channel can be used for something more meaningful, you should do it. And even if there is nothing meaningful to return, you should consider returning void, instead of this.

Comments

if your code needs a lot of comments to be understandable, it probably lacks some structure (split to more methods maybe...). But maybe some of the comments are not necesary, like here:

Node tempNode = localRoot.pop(); // get the last node

I dont think that comment tells me anything I couldn't infer from the code itself...

Passing too many arguments

That's disputable if it's bad to pass a lot of arguments. If they are needed, then they have to be there. If they repeat on more places a simple data structure could be introduced to condense them together.

Passing them as constructor arguments versus passing them as method arguments is not a good question. They have different purpose. The method should accept all arguments that are available to the caller. Constructor should accept all arguments that are only known to the creator of the object.

If some arguments known to the caller are to be passed through constructor, the caller of the method must also be the creator of the object and that's often not true. In fact, if you maintain single responsibility of all classes, it will never be true.

Class fileds are like configuration of the object - how it should behave, what it can offer. Method arguments are more like a specification of what the caller wants.

\$\endgroup\$
1
  • \$\begingroup\$ thanks for your detailed feedback. I was reluctant to use input.process.output approach but I thought it would make it clear what my App is doing. \$\endgroup\$
    – azlan_909
    Commented Jan 3, 2020 at 18:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.