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.
- Problem Set 4 feedback
- Problem Set 5 out: Hope you have read it and gotten started. Due: 3/16
Reminder: send mail to students telling them about the above today!
- Lab 2 postponed until NEXT Thursday the 18th.
- Problem Set 6 will be handed out next Tuesday, the 16th
- PS6 checkpoint on or before Friday the 19th -- described further in the PS6 handout.
- behavioral equivalence and HashMap keys. Why didn't we need to defensively copy incoming and outgoing labels in the BipartiteGraph?
- behavioral equivalence: (Thank you Magda!)
- we do not make defensive copies because nodes and labels are not
part of the representation (we don't care if clients mutate them).
- we can use objects directly as keys because with behavioral
equivalence it is impossible to make equivalent objects become
non-equivalent and non-equivalent objects become equivalent. So no
matter what we mutate, we never change anything used in .equals().
Therefore, we never change anything used in the hashCode() method. So
the hash code remains the same forever.
- Is it okay to have nulls as node labels? For those of you who didn't allow them, how does allowing them change your specs? Your tests? Your implementation?
Magda's answer: (thanks!)
- It is okay to have nulls as nodes and labels.
- This simplifies the specs, and implementation but adds tests as we
now need to test inserting null nodes and labels.
- others?
Pick these apart:
- "A Lane represents a lane of cars, with the same direction and orientation as its underlying RoadSegment"
- "For simplicity, if a car reaches the end of the road segment before the time slice expires (i.e., Car.drive() returns a nonzero time remaining), we'll assume that it wastes the rest of the time slice stopped there."
- What happens to the cars behind it?
- "if a car is stopped at the end of the lane (waiting to get through an intersection), then the car behind it must stop 30 feet back."
- How do you stop a car 30 feet back?
- However far the next car back went, if it's less than 30 feet, make it 30 feet with setFractionOfSegmentTravelled, and do the same to the one after that, and after that...
- What happens if each timeSlice is a year? a nanosecond?
Thanks to Paul Pham for these explanations:
If the timeslice is much larger than the time it takes to
traverse the lane at the speed limit, then the simulation becomes
discrete, less accurate (the errors accumulate when waiting to
turn), but faster to simulate.
Cars are always at the beginnings of lanes and jump
to the end, or at the ends of lanes and turning; since a car
at the beginning of a lane blocks it, this effectively limits
each lane to contain one car in each simulation step.
If the timeslice is much shorter than the lane's traversal time,
the simulation is more accurate but slower and less efficient.
You could imagine that the timeslice is a knob that the user
can twiddle to get more accurate results at the cost of more
computation, or coarser preliminary results more quickly.
- "NOTE: For simplicity, we won't worry about time to accelerate or decelerate; the change in speed happens instantly" -- What happens when a car first enters a Lane? It just sits there until drive() is called.
- NOTE: Car does not .drive() through intersection! The intersection takes it through "instantaneously".
- Recall IntPipes: If a pipe only had one queue instead of having both an input queue and an output queue, then what might happen in the round robin simulation? Answer: An integer could propagate through multiple filters instantaneously.
- "If multiple cars meet these conditions, the intersection should transfer all of them as long as there are no conflicts." -- Can the single-queue problem put a car through multiple intersections at once? (i.e. do you need to design your Lanes the way you did IntPipes?) No. When a car goes into an outgoing lane, it needs to drive() before it can be taken by the next intersection, and round-robin time slicing won't call the Lane's simulate to let it drive until all intersections have finished being simulated.
- Explain real clover leaf intersections for benefit of non-drivers
- Draw a cloverleaf with six inputs/outputs.
- Where can cars go? -- From any input to any output.
- Can they make U-Turns? -- Yes! As on a real highway.
- Green Groups: Either one or else two lights in a green group. Less than one and greater than two are not allowed.
- Unlike Boston: one GreenGroup always green. No delay time.
- Can a car arrive while the light is green, but in fact end up stopping for the red? No. If a car drives up to the intersection, it stops. If the light "is green" then part of the previous time slice's green duration for that lane must remain, and the car will go through (provided there is space in the outgoing lane).
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!
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:
- Parameters - same
- Returns: - same
- Requires: - same
- Modifies: - this.fraction, this.meter
- Effects: - advances the car along the current road
segment... AND increments the meter by the distance travelled and the
time slice.
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!
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:
- Parameters - same
- Returns: - same
- Requires: - same
- Modifies: - this.fraction, this.meter
- Throws: - MaintenanceException if the car becomes undriveable. (e.g. FlatTireException instanceof MaintenanceException)
- Effects: - advances the car along the current road
segment... AND increments the meter by the distance travelled and the
time slice.
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.
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!
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...
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.
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
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();
}
...