CSIT Class 26 Inheritance, continued

We were looking at the Vehicle-Automobile-Truck example, available online here.

Vehicle.java

// Superclass or base class for Automobile and Truck

public class Vehicle

{

    private String make; 

    private String vin;

   

    public Vehicle(String make0, String vin0)        

    {

        make = make0;  

        vin = vin0;

    }

    public String getMake()  {  return make;  }                     

  

    public String getVIN() { return vin; }

}

Automobile.java

public class Automobile extends Vehicle
{
    private String model;
    private int year;
    
    public Automobile(String make0, String model0, int year0, String vin0)
    {
       super(make0, vin0);  ß call the superclass’s constructor to accept make and VIN values
       model = model0;   ß handle our additional subclass fields here
       year = year0;
    }
   
    public String getModel()   { return model; } 
    public int getYear() { return year; }
}
Truck.java
public class Truck extends Vehicle
{
    private int tClass;
    
    public Truck(String make0, int class0, String vin0)         
    {
      super(make0, vin0); 
      tClass = class0;
    }
   
    public int getTruckClass() { return tClass; }
}

 

We looked at some client code VehicleMain.java

We see that the make and VIN are common to all Vehicles, and Automobiles additionally have model and year, while Trucks additionally have a truck class number (from 1 to 8, for bigger and bigger trucks).  This is the same setup as we used earlier when we had a Vehicle interface.  Either way, we end up with a Vehicle type that bridges across Automobiles and Trucks.  With inheritance, we get to put the getMake() and getVIN() code in just one place, the superclass, instead of in both Automobile and Truck as we did with the interface.  That’s “code reuse”.

We looked at the UML class diagram for this inheritance hierarchy:

 

 

Note that the full API for Automobile is everything in the lower Automobile box plus the two methods of Vehicle, getMake() and getVIN()—these two are gained by inheritance.  Similarly the full Truck API is everything in the lower Truck box plus the two methods of Vehicle, getMake() and getVIN(). 

This example is more complex than the Employee example because it has fields, but is also simpler because it has no method overriding.  Method overriding happens when the same method is implemented in both the superclass and the subclass, and the subclass method is the one used for objects of that subclass, overriding the code in the superclass.  Here each method is implemented in just one place.

At this point we worked on the class exercise.

A method for Vehicle that needs the overriding mechanism

Let’s add a method that needs the override mechanism

By federal law, you need a “class B” license to operate a heavy-duty truck, one grossing over 26,000 pounds (loaded up), and thus having truck class 7 or 8, the two highest truck class numbers.  For lighter trucks and all cars, you need a “class C” license.

Let’s implement “getLicenseClass()” for our hierarchy.

First way.  We see that most Vehicles need a class C license, so let’s implement getLicenseClass() of Vehicle to return “C”.  Then override that in Truck to return B or C depending on the truck class.

Vehicle.java: add String getLicenseClass() { return “C”;}

Automobile.java: leave as before

Truck.java: add  the following:

String getTruckClass() {

  if (tClass >= 7) {  // check this truck’s class
       return “B”;  // heavy-duty truck: needs B license

  ] else [

       return “C”;

  }

}

 

We now have the class diagram that shows overriding:

Here the overriding is clear: both Vehicle and Truck have getLicenseClass(). That means that a Truck object, when executing getLicenseClass(), will do the Truck-specific work.

Another way to handle this situation…

Using an abstract method

The above coding is perfectly reasonable. The only worry is that someone reading Vehicle.java might jump to the conclusion that all Vehicles have license class “C”. There’s no indication in Vehicle.java that some subclass overrides this method.

We can mark getLicenseClass method as special, requiring overriding in the subclasses. Then the reader of Vehicle.java will be sure to read the subclasses to see how.

In Vehicle.java we write the following for getLicenseClass:

public abstract String getLicenseClass();   // subclasses must override this method

As soon as we do this for one method in the class, we have to also mark the class with the abstract keyword. We end up with:

// Superclass or base class for Automobile and Truck
public abstract class Vehicle    ßabstract keyword here, on class
{
    private String make;  
    private String vin;
    
    public Vehicle(String make0, String vin0)         
    {
        make = make0;   
        vin = vin0;
    }
    public String getMake()  {  return make;  }                      
   
    public String getVIN() { return vin; }
    public abstract String getLicenseClass();   ßabstract keyword on method, no code
}

 

Once we have made the class an abstract class, we are not allowed to create objects of that exact type, only of subclass type. We still have a constructor, but it is only used via “super” in the subclass constructors.

Now we use the same getLicenseClass() code for Truck as before, and add getLicenseClass() { return “C”; } to Automobile.  See the full implementation in vehicleInterface1

 

Not covered in class: Vehicle and getLicenseClass() in Vehicle are italicized now, to indicate they are abstract (don’t worry about this detail, though)

Getting started on hw5, #9-10.

Here we can work from the class diagram to see that the API for Ticket, the superclass, is supposed to be:

Ticket(number) –construct a Ticket of a given number

getPrice()

toString()

 

so even though a number is specified when the Ticket is constructed, there is no “getNumber()” method. OK, I guess toString should provide the number.

 

Note the statement that no pure Ticket objects will be constructed: this allows Ticket to be abstract if we want.

We also see that ticket prices are given only for subclasses, $50 for WalkupTicket, for example.

 

If we don’t use “abstract”, we need to implement getPrice() for Ticket even though it will never be called, since all the subclasses will override it to provide their specific price. The solution is to return a ridiculous amount:

In Ticket.java: public double getPrice() { return -1; }

 

Then in WalkupTicket: public double getPrice() { return 50;}  //override getPrice in Ticket

 

You can see that adding abstract to getPrice in Ticket cleans up this whole thing:

In Ticket.java: public abstract double getPrice();   // must be overridden in subclasses

And don’t forget abstract on the class.

The eagle-eyed will note the italics on getPrice in the class diagram.