A Simple Calculator for Novice Learning

Joseph Bergin

This page presents the Java code of a calculator I developed to demonstrate what can be done in an object-oriented language without using if or while. It implements the key parts of a simple four function algebraic calculator that has no knowledge of operator precedence. It has been used as the first introduction to Java and object-orientation with a group of adults. There are three parts to the code. The model implements the calculator itself. As such it has only a functional interface. You write code to exercise it. The GUI is completely separate, similar to the model-view-controller architecture. It is built with just AWT. The third part is a set of tests using the JUnit (http://junit.org) testing framework. It was developed using the "test first" methodology advocated in eXtreme Programming (XP -- http://www.xprogramming.org/).

What is shown here was developed by myself in preparation for a two day short course. This code was not shown to students, but rather was re-built live in front of the class using an overhead projector while pair-programming (http://www.pairprogramming.com/) with one of the members of the class. In six hours (three two-hour sessions) we developed most of this except the GUI and the inner NullOperator class. With another hour we could probably have finished what is presented here.

As the code was being built in front of the class, no code was written for the calculator unless we first had a test for it. This was facilitated by using Eclipse (http://eclipse.org) which is both integrated with JUnit and has a refactoring layer. In addition, it writes class and method skeletons with needed constructors, methods to be implemented from interfaces, and JavaDoc skeletons.

Prior to this demonstration, the students were given a two-hour lecture/discussion in which the key ideas (encapsulation, message passing, and polymorphism) were discussed and motivated through various metaphors and role play exercises. The first Java shown was an interface that defined the behavior of the "Actors" in a role play. Nowhere was there any discussion of Java syntax, though questions were taken and answered while the demonstration was going on. The demonstration took about 6 hours total over three sessions. A different student paired with the instructor for each session. Since the students were novices and the instructor took the "driver" role in the pair, the student "navigator" could not contribute much, though several times the student caught errors made by the "driver." The instructor also took suggestions from the "navigator" on method names, etc. I also took time out during the demonstration to discuss and motivate the Strategy and Decorator patterns. The other patterns in the code are Singleton, Null Object, and Immutable. Given more time for refactoring we would have done Typesafe Enumeration as well.

Note that the code design is intentionally not perfect as the students were expected to do some refactoring and add additional features after the demonstration -- again in a pair programming mode. Some of the classes here should be inner to the model, for example. The GUI is also too simple, using only a flow layout to avoid complication.

Presenting the code does not, unfortunately, give you any idea about the path used in its creation. This path involved writing a simple test and running it (it fails of course), writing a bit of code to make the test pass, running the test again, etc. All the while the pair of programmers is discussing issues about the code such as names and structure as well as algorithms. As a result, the code we actually built differs a bit from this. The students were told, by the way, that this wasn't an entirely live demo and that the instructor had prepared before. It was an elaborate "play." However, at one point we did go live and some unanticipated problems arose. This is fine to do, but not if it is extensive at the beginning.

Part of the educational philosophy used in developing this code is that object-orientation is a new paradigm and that students need to be taught early to think in this paradigm. The code solves all of its problems using encapsulation, message passing, and dynamic polymorphism. It does not have a single explicit logical test or selection. Whenever two options occur for an action, different strategy objects are used, with each strategy knowing one of the options. This increases the number of objects and the number of messages, but also makes each piece of the application quite simple.

The problem solved with the strategies is that a number key has to behave differently just after an operator key is hit. At this time it needs to save the display to a save location and clear the display so that the new value can be begun (with the key just hit) rather than just shifting the current value left and appending its own value. Rather than solve this problem with an if, we have two strategies. They switch at well determined times.

There are several reasons for solving the problem this way. With novices we want to teach object-oriented problem solving not imperative. We want this to be the default way of thinking, so we start with it. It helps us avoid "paradigm switch" problems later. In general, however, such solutions increase maintainability of code, since decisions made with if's are often repeated in large programs. This has been found to be a maintenance headache as the testing is duplicated throughout a program (if you make a decision somewhere the chances are high that you will make the same decision elsewhere). Changes to the problem require revisiting all these decision points and the tools don't help you find them in a large program. The technique used here brings all these decison points together in one place so the code has a single point of change as the problem evolves. Furthermore, we find it an advantage in teaching OO to have lots of objects, each simple, sending messages, interpreted polymorphically. We make "delegation" our friend.


An astute reader may notice that in the code the Key classes are actually superfluous. The model class has all of the functionality. The Keys were used to make the code match the metaphor (physical analogy, actually) used as the basis of thinking -- a physical calculator.

A number of "code smells" are also noted in the code. Some of the classes should be inner to the model. Some should be singletons. These were intended to allow student exercises.

Also note that not all of the Keys are in place. This again is to permit very simple student exercises as this is intended for a first exposure to Java. The first exercise was to use cut-paste to create a MinusKey from the given PlusKey. By the way, the student exercises were all done in pair-programming mode, in which the pairs were asked to estimate the time for the task before attempting it, and to develop a JUnit a test for the code before writing the code itself. They recorded actual times as well, of course. About three hours were devoted to exercises, done in the classroom.

The Code

The JUnit tests: CalculatorTest.java

The Calculator itself

CalculatorModel.java

Key.java (an interface)

OperatorKey.Java (a derived interface)

NumberKey.java

PlusKey.java

EqualsKey.java

NumberStrategy.java (an interface)

AccumulateStrategy.java

SaveStrategy.java (a decorator for number strategies)

 

The GUI Calculator.java (developed after everything else)


Download all the code as a zip file: calculator2003.zip


Some Insight

If you are an instructor who learned procedural programming and are struggling with object-oriented programming an insight might help you understand this code and why it is good code. In procedural programming to solve the problem we have here in which the numeric keys must behave (slightly) differently just after an operator key is hit, a flag of some sort would typically be used. I use the term "flag" very generally, though. Sometimes it is an actual variable that you declare for the purpose, which can be given a value to be tested later. Sometimes there is an expression in your program that can be evaluated in a test without declaring any new variables. In both cases I will call this a flag. In object-oriented programming, the key insight is that data (flags as well as other data) can have behavior. Suppose we think of the strategy objects in this program as "flags with behavior." Since we have two values of this "flag" we can attach different behaviors to them. What makes it object-oriented, however, is that, since each object knows its own behavior, we don't need any explicit test to determine which value of the flag we have.We just tell the current value to execute its known behavior.

In this particular example we used interfaces to define the behavior of the strategies. This gives us great flexibility, but inheritance can also be used in many situations. Since the behavor of one of the strategies was a simple extension of the other, we used the Decorator pattern to define the variation, though this was special to this case. Had the behaviors been entirely different, we would have defined a new class for it directly, rather than use a decorator.

Another smaller level insight might help also. In procedural "top-down" programming you often write auxiliary (helper) functions to make your code more comprehensible and to avoid writing long and complicated procedures. In object-oriented programming we do this much less often. Instead, however, we often use delegation in the same situation. Instead of writing a new function to handle a small part of a complex task, we create a new object to do the same. The sub-task is then delegated to the new object, much as a worker might delegate part of her task to a colleague. Again, this object can have behavior, even quite complicated behavior, hidden behind a simple interface. We delegate to strategies here.

Caveat: The above three paragraphs are intended as information for the instructor previously trained in procedural programming. It is not intended as something to teach students. I teach about delegation, not about flags; about polymorphism, not about moving from if tests to polymorphism. It is an effective way to teach students who don't have the same background that their instructors do. It has often been observed that objects are easier for students than for their instructors, since the students don't have to integrate this way of thinking into older thought patterns and habits.


A final note: We had several objectives for this 11 hour course taught over two days. In addition to introducing Java, Eclipse, XP, JUnit, and patterns we wanted to bring the 16 people who had not previously met together into a cohesive group. We wanted them to bond with each other and with the instructors to enable future work. The course is jointly taught by myself and Fred Grossman with assistance from Barry Jones and Ron Frank. For what we did here, four instructors is overkill, however. One person in addition to the instructor would be helpful during the exercise portion so that the students can be given help quickly as needed. Most of the help required was with Eclipse, actually. There was also a bit of confusion about JUnit in this brief introduction and the concept of testing separate from production code. The course lasted longer than the 11 hours described, actually, though most of the rest was busy work for the students. Barry and Ron helped the students get all of the software installed on their laptops, for example. This took longer than we expected.

Last updated: August 22, 2003