Compile Time Dynamism Using Java Generics

Introduction

This article describes the use of generics for compile-time dynamism and its usage for client related type-safety. Usually, the most important aspect during sub-classing is how the override is done to achieve class specific functionality, though with the use of same method parameters. In certain scenarios, you might need to use class-specific parameters. Coupled with this, the overridden methods might use a parameter that itself is the super class of these class-specific parameters. An example of this is methods that are exposed through some public Application Programming Interface and then overridden in concrete implementation classes.

Context of Use

The description of the usage in this article is that of a very frequently occurring scenario that many of you might have faced and devised a similar solution for. The usage is explained with a simple example from the Vehicle Rental system.

Figure 1: Object Relationship Diagram

Here, the main rental service class, RentVehicleManager, could be either an interface or an abstract class. RentCarManager and RentBikeManager subclass from RentVehicleManager and override its methods with their own functionality. The implementation solutions that might have been feasible in this scenario are:

  1. Using the ‘Factory’ pattern to create a Manager Classes.
  2. Use common hierarchy for parameters, with strict-checking in methods.
  3. Usage of common hierarchy along with generics.

Option 1 does provide a good solution for the instance generation of related classes, but does not provide any method object parameter related generality.

For a solution using Option 2 as shown in Figure 2, every overridden method need will now to include strict checking at the start of the method to see whether the argument it actually received is of the type that was expected for this class.

Figure 2: Class Diagram (Using Option 2)

The interface in this case would look like this:

package com.sumithp.codeguru.nongeneric.vehicle;

import com.sumithp.codeguru.vehicle.domain.Vehicle;

public interface RentVehicleMgr {

   public void rentOut(Vehicle vehicle);
   public void checkIn(Vehicle vehicle);
   public void diagnose(Vehicle vehicle);
   public void repair(Vehicle vehicle);
}

A typical Implementation for the Bike Rental in the case of Option 2 is shown here:

package com.sumithp.codeguru.nongeneric.vehicle;

import com.sumithp.codeguru.vehicle.domain.Vehicle;

public class RentBikeMgrImpl implements RentVehicleMgr {

   // If we don't use Vehicle as the parameter here, the clients
   // will not be able to use a generalized interface to call our
   // methods.

   public void rentOut(Vehicle vehicle) {
      // if (vehicle instanceof bike)
         // Renting Out Related DB Operations
   }

   public void checkIn(Vehicle vehicle) {
      // if (vehicle instanceof bike)
         // Vehicle Check In Related DB Operations
   }

   public void diagnose(Vehicle vehicle) {
      // if (vehicle instanceof bike)
         // Self Diagnose functionality of a vehicle
         // Print diagnosis
   }

   public void repair(Vehicle vehicle) {
      // if (vehicle instanceof bike)
         // Perform pre-defined repair
         // Print repair details
   }
}

A compile-time client’s usage in the case of Option 2 typically will be:

package com.sumithp.codeguru.nongeneric.vehicle.client;

import com.sumithp.codeguru.nongeneric.vehicle.RentBikeMgrImpl;
import com.sumithp.codeguru.nongeneric.vehicle.RentCarMgrImpl;
import com.sumithp.codeguru.nongeneric.vehicle.RentVehicleMgr;
import com.sumithp.codeguru.vehicle.domain.Bike;
import com.sumithp.codeguru.vehicle.domain.Car;
import com.sumithp.codeguru.vehicle.domain.Vehicle;

public class RentNonGenericVehicleClient {

   public void rentBike() {

      // You want only one interface to handle all rentals
      RentVehicleMgr rentVehicleMgr;

      rentVehicleMgr = new RentBikeMgrImpl();

      Vehicle vehicle = new Bike(104,"TWO",true,150);
      rentVehicleMgr.rentOut(vehicle);

      /*
       * Client can as well do this
       *
       * Vehicle vehicle = new Car(104,"FOUR",true,"PETROL");
       * rentVehicleMgr.rentOut(vehicle);
       *
       * If there are no instanceof checks, this bombs!
       *
       */
   }

   public void rentCar() {

      // You want only one interface to handle all rentals
      RentVehicleMgr rentVehicleMgr;

      rentVehicleMgr = new RentCarMgrImpl();

      Vehicle vehicle = new Car(104,"FOUR",true,"PETROL");
      rentVehicleMgr.rentOut(vehicle);

      /*
       * Client can do the same as shown for rentBike()
       *
       * Vehicle vehicle = new Bike(104,"TWO",true,150);
       * rentVehicleMgr.rentOut(vehicle);
       *
       * If there are no instanceof checks, this bombs too!
       *
       */
   }

}

Option 3 is the most complete and viable alternative because the usage is more coherent by exposing the methods in a single class, each using their implementation along with their own generic variable. Take a closer look at this solution.

More by Author

Must Read