What is programming for change? The development of any piece of software
begins with a set of specifications describing in some detail the problems to
be solved. Almost invariably, the specifications fluctuate and change
during the course of development of the software. Trying to anticipate
all possible changes and write a program that does all and covers all is a futile
undertaking. However, it is not unreasonable to expect and even demand
that programs be written in such a way that an "epsilon" (read "small")
change in the specifications will only necessitate a "delta" (read
"manageable") change in code. Though "small" and "manageable"
are software characteristics with subjective interpretations based on differing
software engineering metrics, at some basic level they can be expressed in terms
of lines of code and "big Oh" analysis.
Programming for change is a continual process in which a software system is
designed and re-designed over many iterations to capture the essence of the
problems at hand and express it in code. At the heart of this process
is the incessant effort to identify those elements that can vary —the
variants— and delineate them from those that do not —the
invariants. A properly designed software system should strive to
decouple the variants from the invariants in order to facilitate the re-use
of the invariants and allow modifications to the variants with minimal perturbation
to the existing code.
To illustrate the importance of programing for change to the students, we guide
them through the development of a program that converts temperature measurements
from one unit to another. The programming assignment consists of a series
of small exercises, each of which imposes a small change in the requirements
and forces some appropriate modification on the implementation code. To
promote code re-use, we apply the Janus Principle ([1])
and require that the program at each phase must be written in such a way that
it can support at least two distinct user interfaces: a GUI interface and a
command line interface. For certain specification changes, we ask students
to identify the variants and the invariants and make appropriate modifications
to the code. In many situations, we require the students to modify their
code in more than one way and discuss the pros and cons. The programming
assignment is summarized in the following.
Preliminary step: Write a program to convert 35°C to Fahrenheit.
We provide the student the conversion formula and what we call the "constant
solution" in GUI form and command line form.
Exercise 2: Write a program to convert temperatures between Celsius
and Fahrenheit in both directions.
Here we require the students to add another static method for the conversion
from Fahrenheit to Celsius. This way of modifying the model to comply
with the change in the problem specification will call for numerous changes
in the GUI and in the command line interface.
Exercise 3: Write a program to convert temperatures between Celsius,
Fahrenheit and Kelvin in all directions.
Again we ask the students to add more static methods here as done in
the preceding exercise. The students should experience something
"bad" here: an epsilon change in the specification is engendering
a large change in code with the current design.
Exercise 4: Discuss the change in complexity between the solutions
to exercises 2 and 3. Derive a formula that expresses the complexity
of a similar program involving N different temperature scales.
We make the students do some informal analysis here. We want them
to stop writing code and think.
Exercise 5: Write a program converting Celsius, Fahrenheit and Kelvin
in all directions by viewing the N different temperature scales as nodes of
a star-shaped graph. Discuss the complexity.
By changing the model to reflect the notion of a star-shaped graph,
the students should experience at this point that an epsilon change in
the specification only induces a delta change in the code for the conversion.
Still the code for the user interfaces has to be modified more than desired.
Moreover, most of the existing code has to be modified. The nagging
question is whether or not there is a design that will allow us to add
minimal code to the system without touching any of
the existing code. This leads to the next exercise that guides
students to capture the essence of the problem and capture its abstraction
with appropriate concrete and abstract classes.
The program looks the same as the one from Exercise 3, but its implementation
is different (download solution)
Exercise 6: Complete the provided stub code that implements
the following object model for the conversion problem.
A conversion function is an abstract bijection (i.e. a function with
an inverse) from a subset of real numbers to another subset of real numbers
A measurement has a unit and a real value.
A unit is an abstract notion. It has a symbol. It has the abstract
capability to provide a conversion function to convert from itself to
Celsius, a concrete unit. It knows concretely how to convert from one
measurement to another measurement.
While we do not require this from our students, the demonstration program
above also demonstrates the use of Java reflection to load classes at
runtime. This demonstrates that the design is flexible enough to deal
with temperature scales that were not incorporated right away. Try clicking
on the "Add Unit..." button and enter "model.temperature.Reaumur"
to load the Reaumur temperature scale at runtime.
We also show our students that this design is not limited to converting
temperatures, but that it can be applied to any unit conversion problem.
The category of the units (temperature, length, volume, etc.) doesn't
even have to be known, and new categories can be added at runtime. This
introduces new challenges, like preventing conversion of liters to inches,
but they can be solved too, as the demo applet below proves (download
source).
These are the only units available in this demo, but if you run
the demo from the command line, you can add any class accessible
on the classpath.
We provide the students with stub code to ensure that they will do exactly
what we ask them to do. The students will come to realize that should they
need to add another temperature unit such as Reaumur, they only need to
subclass the abstract unit class and implement only one method: the concrete
factory method that provides the conversion from Reaumur to Celsius and
back. Nothing else in the existing code needs to be changed.
With a simple application of Java reflection, students could dynamically
load any concrete unit class at run-time without recompiling
any of the existing code.
In summary, to be able to program for change, one should be able to capture
the essence of the problem, separate the variants from the invariants and
program to the highest level of abstraction.
What makes it so nifty?
Teaches students about object-oriented design on a system that is rich enough
to involve a multitude of design issues but small enough to be manageable
by introductory level students. The system the students work on is familiar
to the students and one to which they can relate.
Forces students to consider flexibility, extensibility and robustness and
apply appropriate design patterns to achieve in these design goals.
Help students develop a sense and an appreciation for good designs by forcing
them to write both good and bad designs and compare them against each
other.
Requires students to integrate a broad spectrum of skills and concepts (see skills list below).
Focuses on the thought processes involved with the design of OO systems.
Has an open-ended number of design solutions, each with its own trade-offs
and pros and cons, that serve as means to stimulate the students' critical
thinking.
Channels the students down a path that fosters good object-oriented design
by setting requirements that cut off the vast majority of inappropriate design
possibilities.
Reinforces the concept of an abstract function as a model for an abstract
computation.
Target audience
This project is intended for students in the second semester of an objects-first
curriculum where they have already seen polymorphism, design patterns and component
framework systems (see prerequisites below).
Ideas and Skills Involved
The Temperature Calculator is not really about converting temperature values
to different units. Instead, it teaches students some of the bigger concepts
in software engineering, such as:
Striving to capture appropriate abstractions and delineate the invariants
from the variants.
Creating loose and abstract coupling between cooperating objects in the
system to achieve correctness, robustness, security, flexibility, and extensibility.
Using and building components and frameworks.
Using anonymous inner classes to instantiate objects on-the-fly, especially
in the context of commands.
Using the closure properties of inner classes to directly access both instance
and final local variables.
Numerous compelling demonstrations of polymorphic behavior, especially those
that are difficult to replicate with conditional statements.
Length of time students typically work on it
We have a 1.5 hour lab to help students get started on the
assignment. We expect students to spend from 6 to 8 hours on the project.
We give them a week to complete the project.
Prerequisite material
This project assumes the following material has been covered in class:
Javadoc and UML diagrams
Abstract structure and behavior
Polymorphism
Using both inheritance and composition as means of extending functionality
Delegation model programming
Design patterns, particularly MVC, factory method, template method and command
Closure and anonymous inner classes
Developing simple GUIs with Swing.
Rudiments of complexity analysis using big Oh notation.
Rudiments of Java generics.
It is important that this assignment be given in the context of a comprehensive
instruction on object-oriented programming that stresses abstract decomposition.
Difficulties to watch for
Understanding the modeling of behaviors as objects so that they can be passed
to other objects and executed later.