Class Specification and Implementation Review, part 2

Purpose:

In a previous lab you wrote the specification of the class NestedRectangles and generated a browsable specification document for this class. In this lab, you will complete the implementation and testing of this class.

Implement the class NestedRectangles:

Declare instance variables:

An instance of NestedRectangles will use two instances of the class Rectangle.

Implement constructors:

Recall that an class invariant requires that the dimensions of the inner rectangle must be strictly less than the corresponding dimensions of the outer rectangle. This invariant places requirements on the constructor arguments provided by the client.

Implement queries:

Implement commands:

Analysis of the client use of an instance of NestedRectangles:

How does a client make sure preconditions are satisfied, for instance when invoking the command

    /**
     * Set the width of the inner Rectangle to the specified value.
     *   require: 
     *     0 < value && value < this.outerWidth()
     *   ensure:
     *     this.innerWidth() == value
     */
    public void setInnerWidth (int value) { ... }

The client must to know the current width of the outer rectangle, and that the argument is positive and less than that width. So, since the width can change at any time, the client should write code like the following to change the outer width:

    if (myValue > 0 && myValue < nestedRec.outerWidth()) {
        nestedRec.setInnerWidth(myValue);
        ...
    }

The client may sometimes be able to assert that one or both conditions are satisfied by the argument. For instance:

    int myValue = nestedRec.outerWidth() - 10;
    // assert: myValue < outerWidth()
    if (myValue > 0) {
        nestedRec.setInnerWidth(myValue);
        ...
    }

This type of code must be written by the client to ensure that preconditions are not violated. If the client is a user interface, then this kind of code will appear in all places where the user interface wants to change the size of one of the rectangles. The client must guarantee that preconditions are satisfied, and this generally means wrapping changes in conditionals.

The problem is that this kind of code is bound to create maintenance headaches!

Supposed you write a user interface and when involving commands you write them using the code suggested above. The code is fine and the user interface will work as expected, because it does not break any preconditions. In other words, every time the user interface wants to issue a command, it explicitly checks the preconditions before doing it. "Fine code" you might say.

The headaches come when you must modify NestedRectangles. Suppose you must modify the application to accommodate cases where the inner rectangle can have the same dimensions as the outer rectangle. The changes required in the definition of NestedRectangles are minimal. For example, if setInnerWidth is written as

    /**
     * Set the width of the inner Rectangle to the specified value.
     *   require: 
     *     0 < value && value < this.outerWidth()
     *   ensure:
     *     this.innerWidth() == value
     */
    public void setInnerWidth (int value) {
        Require.condition(0 < value && value < this.outerWidth());
        innerRec.setWidth(value);
    }

the changes are straightforward:

    /**
     * Set the width of the inner Rectangle to the specified value.
     *   require: 
     *     0 < value && value <= this.outerWidth()
     *   ensure:
     *     this.innerWidth() == value
     */
    public void setInnerWidth (int value) {
        Require.condition(0 < value && value <= this.outerWidth());
        innerRec.setWidth(value);
    }

This is fine and relatively simple, and the changes are limited to the definition of NestedRectangles.

Here is the problem: what about the clients of NestedRectangles? Each is checking that the dimensions of the inner rectangle must be less than the corresponding dimensions of the outer rectangle! All that code needs to be maintained.

A server does not know the number of clients that it will have, nor should it care. And for a given client, we cannot tell where commands are used without a careful examination of the code. It all depends on the design of the client. The fact is that changing NestedRectangles creates problems for every client.

A solution:

The cause of the problem is that it was left up to the clients of NestedRectangles to explicitly check the validity conditions regarding the relationship between the inner and outer rectangles.

The actual code to check the validity of this relationship should be provided by NestedRectangles itself because it is information about the NestedRectangles instance. NestedRectangles should provide queries like the following:

    /**
     * The specified value is a legal width for the inner rectangle.
     */
    public boolean isValidInnerWidth (int value) { ... }

The query will implement the current policy for the width of the inner rectangle: less than the outer width, less or equal to the outer width, less than but no less that 1/5 of the outer width, less than the outer width and less than or equal to the inner height, etc.

Now rather than including code for that implements the current NestedRectangles policy, the client contains code like the following:

    if (nestedRec.isValidInnerWidth(myValue)) {
        nestedRec.setInnerWidth(myValue);
        ...
    }

This code will not break if the condition is changed. Only the code of the server, NestedRectangles, will need to change.

The same situation exists with regard to changing colors.

Note that the method swapColors does not have any preconditions.

A complete specification of NestedRectangles can found here.

Testing and test plans:

We now need to design a test plan to test NestedRectangles.

Post-lab:

Submit the following: