6.170 Recitation 6: Traffick and Inheritance

Thursday, March 11, 2004

Contents:

As usual, please read carefully beforehand and focus on the parts you find most relevant.

Administrivia

Problem Set 4 Notes

Problem Set 5 Discussion and Hints

Lanes

Pick these apart:

Intersections

Clover Leaf

Traffic Light

Inheritance

Having completed the Traffick simulator assignment in 6.170, you are soon snatched up by Boston's Clover Cab Company (``It's easy going Green'') to help them analyze change in profits from several proposed traffic changes.

To calculate revenue in the adapted simulation you will have to simulate both the cars that get in the way and the cabs themselves. So you have two kinds of work objects: Cars and Cabs.

Of course you would like to duplicate as little code as possible!

Is a Cab a *true subtype* of Car?

Intuition: A Cab is just a Car with a Meter:

Car.drive:

public double drive(double timeSlice)

    Simulates driving the car along the current road segment for a period of time.
    Parameters:
        timeSlice - amount of time to simulate, in seconds 
    Returns:
        amount of timeSlice left over if car reaches end of current segment before timeSlice has fully elapsed; or 0, if timeSlice elapses before car reaches end of current segment.
    Requires:
        timeSlice >= 0
    Effects:
        advances the car along the current road segment at its current speed for (at most) timeSlice seconds.
    Modifies:
        this.fraction

Cab.drive:

NOTE: conjunctive condition okay!

Provide additional observers to query the meter, mutators to turn it on and off?
This is okay. So far still a true subtype.

Provide mutators to change routeRemaining en route?
sure: let Cab support newDestination(Route), and newCustomer(Route) - imagine what you will.

None of these make a cab non-substitutable.

Good example of inheritence? NO!!

Breaks the invariant requirement: A subtype must ensure that it does not break its supertype's Representation Invariant!

By changing the routeRemaining, we break Car's RI: remainingRoute must be a suffix of fullRoute at all times.

Nonetheless, it works, and nobody notices the violation!

Breaking the Example

You did such a good job that they hire you to convert your simulator to a tracking system. You still simulate the other cars based on local traffic patterns, but the Cabs now need to track real cabs and real calls out on the road. This means they also need to take into account things like maintenance schedules. There's already a bunch of java code for doing that in their company, in a class called a MaintenanceObject.

You want your Cab to be both a Car and a MaintenanceObject.

Obviously it can't extend both classes. We'll get to that in a minute. Can a Cab remain a Car while adding MaintenanceObject functionality? Probably not:

new Cab.drive:

NOTE: @throws is a DISJUNCTIVE effect. It EITHER has the effect, OR throws an exception, not both.

Now a Cab is no longer substitutable anywhere you want a Car, because Cars won't ever throw a MaintenanceException.

Vehicle and Maintainable

Now let's address the question: How can the Cab appear to clients as both a Car and a MaintenanceObject?

By transforming the parent classes into interfaces ("Specifications" in MDD terms) that the Cab can implement.

But Interfaces cannot store any implementation, so Cab now must contain all the actual implementation for BOTH a Vehicle and a Maintainable!

AbstractVehicle and AbstractMaintainable

But we can associate each Interface with a skeletal implementation from which other classes that want to implement the interface can inherit.

A skeletal implementation is an abstract class that implements as much functionality as it can for an interface. By convention, it is named "Abstract" followed by the name of the interface. See for example AbstractList, AbstractCollection, AbstractMap. (Bloch p.87)

Aside: the javadoc for a skeletal interface is very different from the javadoc for any other class, because a skeletal interface must be designed to be extended. Where most javadoc is intended to encapsulate the implementation and provide an abstraction for it, a skeletal implementation must document its implementation choices for the Interface that it implements. It is vital, e.g. to document calls from one method to another, as in the case of addAll calling add. Recall the example from lecture where we wanted to instrument a HashSet to tell how often add was called...

Cab Contains a Car

Is the MDD above valid? -- no. Cab now extends two classes. This will not compile.

But it can instead contain the Vehicle class, and create new methods like drive(timeSlice) that will forward the call to the internal AbstractVehicle.

tool support for wrapping in Eclipse

Wait! You can't contain an abstract class because it can't be instantiated!

Good point, sharp student! What else might we do? Perhaps contain the Car instead?

isn't throwing that extra exception still a problem?

No, because the Car is no longer a superclass, so we don't need to be substitutable for it.

Van and Sedan

But the Simulator uses Vehicles, so if I want to simulate cabs, I need the Cab to be a Vehicle. -- okay:

Van and Sedan now extend AbstractVehicle, and Cab both contains and implements a Vehicle!

isn't throwing that extra exception still a problem?
NOW it is!!! Now that Cab implements Vehicle, assuming Vehicle's spec is the same as Car's was originally. When a class implements an interface, it must also be substitutable. This is where we will have to bite the bullet and weaken the spec to accept Exceptions.

You've now enabled the company to charge more for their Van service, and you get a big bonus!

Good Luck on PS5, Lab next Thursday

Delegation with Eclipse

Source >> Generate Delegate Methods.  If your class has 
any fields in it:

public class Cab {
     private Car car;
}

then invoking this menu option will pop up a dialog box showing each 
private field's methods.  You just pick the methods you want to delegate:

    car
        X drive()
        X getColor()
        X makeTurn()
          ...

and Eclipse generates the delegation methods:

     public double drive(double timeSlice) {
         return car.drive(timeSlice);
     }

     public Color getColor() {
         return car.getColor();
     }

     public void makeTurn() {
         car.makeTurn();
     }

     ...