A calculator is an application which allows its user to enter a mathematical expression. It then calculates the expression and displays the results.

In our calculator application, we do not have interaction with the user (called “UI”). We do not write a program, but rather definition classes. Anyone who writes a user interface application for a calculator can use our classes to calculate the expressions entered by the user, and show them the results. We only provide them with concrete classes which represent each of the possible expression elements, and an interface they can use to know which methods those classes support.

Yes, it’s very easy to ask a user for an arithmetic expression, parse the string, convert the numbers to doubles and run the operations with simple if..else blocks, all in a single main method. However, that would be considered bad design. The reason it’s a bad design, is that one long method (or many methods) is very hard to maintain. That’s why we will usually design a calculator with several classes. We will utilize Java’s inheritance and polymorphism tools to make it an elegant and easy to maintain design.

The calculator we will implement is based on the design given below. What you see is called a “class diagram”, which shows the different entities in the calculator and their relationships. A dotted arrow means “implements” while a solid arrow means “extends”. For example, the abstract class Operator implements the ExpressionElement interface.

Study the class diagram below, and make sure you understand the different entities and relationships.

Note: the API of the entire exercise is given here.

Below is an example of how to calculate the expression: ((15.4-3)*2)+(4*(1+5))

            // define literals

            Literal l1 = new Literal(1);

            Literal l2 = new Literal(2);

            Literal l3 = new Literal(3);

            Literal l4 = new Literal(4);

            Literal l5 = new Literal(5);

            Literal l15 = new Literal(15.4);

           

            // build the expression

            OperatorAdd a1 = new OperatorAdd(l1, l5);             // 1+5

            OperatorMultiply m1 = new OperatorMultiply(l4, a1);   // 4*(1+5)

            OperatorSubtract s1 = new OperatorSubtract(l15, l3);  // 15.4-3

            OperatorMultiply m2 = new OperatorMultiply(s1, l2);   // (15.4-3)*2

            OperatorAdd a2 = new OperatorAdd(m2, m1);             // ((15.4-3)*2)+(4*(1+5))

           

            // calculate and print

            System.out.println(a2 + " = " + a2.evaluate());

 

An expression element is an element which can be found in a mathematical expression. Such element can be, for example, a plus sign, a minus sign, a number, and so on. A mathematical expression is a composition of different expression elements.

Each such element can be both presented as a string, or it can be evaluated. The evaluation of a literal (just a number) is just the number. The evaluation of a plus operator, is the result of adding its left operand to the right operand, both are also expression elements.

As you can see in the class diagram, all the classes in our little project implement this interface.

1.       Write the interface ExpressionElement in package calc, according to the API.

·         Remember that interface methods are like any other method, only without the visibility modifier, the abstract modifier and without the implementation. Every interface method is considered by Java public abstract, without us needing to write it explicitly.

·         Don’t forget to Javadoc your interface. Write a class comment above the interface declaration and method comments above each method.

The Literal is a concrete class, which means you can instantiate it (“concrete” is an adjective, not a Java keyword).

The Literal class represents a number in an arithmetic expression. In our calculator implementation, all the numbers are real, and we use ‘double’ as the standard type.

1.       Write a class called Literal in package calc, which implements the interface ExpressionElement.

2.       Implement both methods provided by the interface, and a constructor. Adhere to the given API.

 

·         The class realizes (implements) the ExpressionElement interface, which means you have to implement all the interface methods.

·         Pay special attention to the toString requirements!

At this stage, you can already test your code. Yay! J

Write a class Calculator in the default package (or any other package which is not calc). Please do not submit this file. Write a main method in the class, and try out this code:

            ArrayList<ExpressionElement> elements = new ArrayList<ExpressionElement>();

            elements.add(new Literal(14));

            elements.add(new Literal(0));

            elements.add(new Literal(-4));

            elements.add(new Literal(13.1415));

            elements.add(new Literal(-0.1));

            for (ExpressionElement element : elements) {

                  System.out.println(element + " = " + element.evaluate());

            }

This code represents polymorphism in its purest form: although ‘elements’ is an ArrayList of references to ExpressionElements, we can add references to other types, as long as they inherit ExpressionElement. Moreover, the calls to toString and evaluate invoke the implementations within the Literal class, because at runtime, the references point to objects of type Literal.

The results should look exactly like this:

14 = 14.0

0 = 0.0

-4 = -4.0

13.1415 = 13.1415

-0.1 = -0.1

Although Operator implements the ExpressionElement interface, it’s not a concrete class, which means you can’t instantiate an object of type Operator.

Operator is an abstract class which represents arithmetic operators. An arithmetic operator is any arithmetic operation which can be acted upon one or more operands. Some examples:

-          Addition is the operator which adds two operands. Its symbol is “+”.

-          Division is the operator which divides two operands. Its symbol is “/”.

-          Square root is the operator which finds the square root of a single operand. Its symbol is “” (since you can’t save this symbol in your code, use “sqrt” or alike).

As you can see, an operator may require one operand or two (or more). This class doesn’t care. It simply represents a general operator, or an “abstract” operator.

1.       Write an abstract class Operator in package calc, which implements the interface ExpressionElement.

2.       Implement the class according to the given API.

-          Although you can’t instantiate an abstract class, you will have to write a constructor to Operator. The reason is that the class has implementation, such as holding the operator symbol (something common to all operators). The constructor will be called by derived classes in order to set that symbol.

-          Spare a minute to learn the API documentation. You have to implement all the methods given in the table there, except the abstract ones. Abstract methods should be written as a method declaration only.

-          Since some of the interface methods are not implemented in Operator (namely toString), the classes which inherit Operator will have to implement them.

-          Implementing evaluate may be confusing. We will let you think it over. However, here’s a hint: you can call another method (like operate) even if it’s not implemented yet. It’s enough to have it declared. At runtime, inheritance will make sure the correct implementation runs.

Since Operator is abstract, we can’t instantiate it. Therefore we can’t really test the code. Testing will have to wait until we have written a concrete class.

 

As you’ve seen in the description of class Operator, there can be several types of an operator. An operator can require a single operand (a unary operator), two operands (a binary operator) or more. We will reflect this generalization by adding another level of abstract classes, a class for each operator type. All of them will inherit from Operator.

In this section you will implement a binary operator. A binary operator requires two operands (such as the addition operator “+”).

A typical binary operator differentiates its operands according to where they’re written: a left operand and a right operand. This may be unimportant (like in the case of adding operands) or important (like in the case of subtraction, division or power). However, we will reflect the differentiation in the abstract BinaryOperator class, by naming the operands accordingly (left/right).

1.       Write an abstract class BinaryOperator in package calc, which extends the Operator class.

2.       Implement its methods according to the API.

·         Pay special attention to the requirements in toString. Use the instanceof keyword to find out whether an operand is a literal or not.

·         Here too, the constructor will not be called by a user (the class is abstract and can’t be instantiated). However, you must implement a constructor, which should call the implemented constructor of Operator (remember super?).

·         setOperands may be used by derived classes. Don’t forget to validate the parameters!

This is a concrete class. It will be used by anyone who wants to reflect the subtraction operator (“minus”, “-“) within an expression.

1.       Write a class called OpeartorSubtract in package calc, and implement its API.

As you can see, you only have to implement 2 methods:

·         The constructor sets the operator symbol by passing it to the super constructor. The minus symbol is “-“.

·         The operate method runs the actual calculation of left_operand – right_operand.

·         You can find hints as to which methods to use in operate in its API documentation. Use these hints!

It’s time to test everything that was not yet tested.

Change your code to the one given below, learn it, and compare the results with ours:

            // our test list

            ArrayList<ExpressionElement> elements = new ArrayList<ExpressionElement>();

           

            // add literals

            elements.add(new Literal(14));

            elements.add(new Literal(0));

            elements.add(new Literal(-4));

            elements.add(new Literal(13.1415));

            elements.add(new Literal(-0.1));

           

            // create subtraction operators

            ExpressionElement sub0 = new OperatorSubtract(elements.get(2), elements.get(2));

            ExpressionElement sub1 = new OperatorSubtract(sub0, elements.get(2));

            ExpressionElement sub2 = new OperatorSubtract(elements.get(0), sub1);

            ExpressionElement sub3 = new OperatorSubtract(elements.get(3), sub1);

            ExpressionElement sub4 = new OperatorSubtract(sub3, elements.get(4));

           

            // add subtraction operators

            elements.add(sub0);

            elements.add(sub1);

            elements.add(sub2);

            elements.add(sub3);

            elements.add(sub4);

           

            // test

            for (ExpressionElement element : elements) {

                  System.out.println(element + " = " + element.evaluate());

            }

Desired output:

14 = 14.0

0 = 0.0

-4 = -4.0

13.1415 = 13.1415

-0.1 = -0.1

-4--4 = 0.0

(-4--4)--4 = 4.0

14-((-4--4)--4) = 10.0

13.1415-((-4--4)--4) = 9.1415

(13.1415-((-4--4)--4))--0.1 = 9.2415

 

Now that you have implemented a concrete operator with all its ancestors, you can implement the rest of the classes, i.e. all other 3 binary operators, the UnaryOperator abstract class and its subclass OperatorSquareRoot.

Start by implementing more concrete classes (binary operators). After implementing each one, test it. Don’t leave the testing to the end!

Pay special attention to:

-          Which method should be implemented in which class.

-          Which method should be implemented and which is abstract.

-          The API documentation requirements.

-          Some operators can’t operator on negative or zero operands. Pay attention!

Modify the testing code provided to include complex arithmetic expressions. With each new concrete class you implement, spend some time in testing it.

·         In this section you will expand the class diagram by implementing more classes.

·         The objective of this section is to let you make your own decisions regarding inheritance design and coding.

·         You will have to think and decide which methods to implement in which class, which should be abstract, what method visibility to use and other good stuff.

·         By writing your own classes you will be able to realize how easy and elegant it is to add functionality to a well-designed software.

1.       Implement another binary operator. Mathematics provides many (n-th root, log, power, modulo, etc).

2.       Implement another unary operator (cubic root, alternate sign, etc).

3.       Write an abstract class called TernaryOperator, for operators which require 3 operands.

4.       Write 2 concrete classes which Implement ternary operators. You can make them up.

5.       Implement another concrete class which implements the ExpressionElement interface directly. Think of mathematic constants, such as PI, e, etc..

As usual, follow the styling and submission instructions detailed in our website.

Submit both a soft copy and a hard copy, both absolutely identical.

Submit all the classes in the following class diagram (your own classes are in yellow):

·         Ex09.zip (file):

o   calc (folder with 16 files)

§  ExpressionElement.java

§  Literal.java

§  Operator.java

§  BinaryOperator.java

§  OperatorAdd.java

§  OperatorSubtract.java

§  OperatorMultiply.java

§  OperatorDivide.java

§  Operator<your own binary operator>.java

§  UnaryOperator.java

§  OperatorSquareRoot.java

§  Operator<your own unary operator>.java

§  TernaryOperator.java

§  Operator<your own ternary operator 1>.java

§  Operator<your own ternary operator 2>.java

§  <Your own concrete class which implements the interface>.java

 

 

Good luck!! 

 

© Boaz Kantor