(See Assignment 2 Solution for the code from which this was derived.) The Driver class still handles I/O. The Cell class can now contain a Formula in addition to a String or Double; it does this via a generic Object reference. The Formula class uses a utility class, FormulaParser, to do the actual parsing and evaluation of formulas.
The FormulaParser class needs to be able to translate a reference such as "A3" to the value at that cell. To decouple the FormulaParser from the Spreadsheet class (which has the responsibility for translating such references) and avoid a circular reference in the class dependencies, I introduced the Dereferencer interface. It provides a single method, getValue(name), which all the kinds of Spreadsheets implement. FormulaParser is dependent on Dereferencer, and so is Spreadsheet, so there is no circular dependency. (Follow the arrows in the diagram below to make sure of this -- the diamond shape should be read as an arrow pointing away from the diamond.)
Spreadsheet is now an interface rather than a class; it could just as easily have been a base class. Two implementations of Spreadsheet exist, ArraySpreadsheet and SparseSpreadsheet. To make sure that changes to the code haven't broken anything, the Test class tests both ArraySpreadsheet and SparseSpreadsheet with a small "smoke test".
To handle the problem of indicating whether a string is supposed to be just a string or a formula, I adopted the convention that a string beginning with a "=" is a formula. So "set A1 =+ 1 1" attaches the formula (+ 1 1) to A1.
Circular reference checking is done inside Formula, which maintains a flag for that purpose. Other error detection for formula-related errors is done inside FormulaParser, which throws subclasses of the FormulaException class (exception classes are not shown on the diagram for clarity). Handling of these errors is deferred to the Cell class, which takes care of translating errors into #ERROR strings for display purposes.
Click on a class to see the code.