samedi 18 février 2017

Interface with variable argument types

I have java interface and class implementations that have need for different arguments when invoking similar behavior. Which of the following is mostly appropriate?

In first option I have different classes inherit common behavior from base interface and all differences are only implemented directly in the classes and not in interface. This one seems most appropriate, but I have to do manual type-cast in the code.

public class VaryParam1 {

    static Map<VehicleType, Vehicle> list = new HashMap<>();

    public static void main(String[] args) {
        list.put(VehicleType.WITHOUT_TRAILER, new Car());
        list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());

        ((Car)list.get(VehicleType.WITHOUT_TRAILER)).drive(1); //ok - but needed manual cast
        ((TruckWithTrailer)list.get(VehicleType.WITH_TRAILER)).drive(1, 1); //ok - but needed manual cast
    }
}

enum VehicleType {
    WITHOUT_TRAILER,
    WITH_TRAILER;
}

interface Vehicle{
    //definition of all common methods
}

class Car implements Vehicle {

    public void drive(int numberOfDoors) {
        System.out.println(numberOfDoors);
    }
}

class TruckWithTrailer implements Vehicle {

    public void drive(int numberOfDoors, int numberOfTrailers) {
        System.out.println(numberOfDoors + numberOfTrailers);
    }
}

In second option I have moved methods one level up to the interface, but now I need to implement behavior with UnsupportedOpException. This looks like code smell. In code, I don't have to do manual casting, but I also have possibility to call methods that will produce exception in run time - no compile time checking. This is not that big problem - only this methods with exception that look like code smell. Is this way of implementation best practice?

public class VaryParam2 {

    static Map<VehicleType, Vehicle> list = new HashMap<>();

    public static void main(String[] args) {
        list.put(VehicleType.WITHOUT_TRAILER, new Car());
        list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());

        list.get(VehicleType.WITHOUT_TRAILER).drive(1); //works
        list.get(VehicleType.WITH_TRAILER).drive(1, 1); //works

        list.get(VehicleType.WITHOUT_TRAILER).drive(1, 1); //ok - exception - passing trailer when no trailer - no compile time check!
        list.get(VehicleType.WITH_TRAILER).drive(1); //ok - exception - calling trailer without trailer args - no compile time check!
    }
}

enum VehicleType {
    WITHOUT_TRAILER,
    WITH_TRAILER;
}

interface Vehicle{
    void drive(int numberOfDoors);
    void drive(int numberOfDoors, int numberOfTrailers);    //code smell - not valid for all vehicles??
}

class Car implements Vehicle {

    @Override
    public void drive(int numberOfDoors) {
        System.out.println(numberOfDoors);
    }

    @Override
    public void drive(int numberOfDoors, int numberOfTrailers) {    //code smell ??
        throw new UnsupportedOperationException("Car has no trailer");
    }
}

class TruckWithTrailer implements Vehicle {

    @Override
    public void drive(int numberOfDoors) {  //code smell ??
        throw new UnsupportedOperationException("What to do with the trailer?");
    }

    @Override
    public void drive(int numberOfDoors, int numberOfTrailers) {
        System.out.println(numberOfDoors + numberOfTrailers);
    }
}

Here I used generics in order to have common method in interface, and parameter type is decided in each class implementation. Problem here is that I have unchecked calls to drive. This is more-less similar to problem of direct casting in option 1. Bur here I also have possibility to call methods that I should not be able to!

public class VaryParam3 {

    static Map<VehicleType, Vehicle> list = new HashMap<>();


    public static void main(String[] args) {
        list.put(VehicleType.WITHOUT_TRAILER, new Car());
        list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());

        list.get(VehicleType.WITHOUT_TRAILER).drive(new VehicleParam());    //works but unchecked call
        list.get(VehicleType.WITH_TRAILER).drive(new TruckWithTrailerParam());    //works but unchecked call

        list.get(VehicleType.WITHOUT_TRAILER).drive(new TruckWithTrailerParam()); //works but should not!
        list.get(VehicleType.WITH_TRAILER).drive(new VehicleParam());   //ClassCastException in runtime - ok but no compile time check
    }
}

enum VehicleType {
    WITHOUT_TRAILER,
    WITH_TRAILER;
}

class VehicleParam {
    int numberOfDoors;
}

class TruckWithTrailerParam extends VehicleParam {
    int numberOfTrailers;
}

interface Vehicle<T extends VehicleParam>{
    void drive(T param);
}

class Car implements Vehicle<VehicleParam> {

    @Override
    public void drive(VehicleParam param) {
        System.out.println(param.numberOfDoors);
    }
}

class TruckWithTrailer implements Vehicle<TruckWithTrailerParam> {

    @Override
    public void drive(TruckWithTrailerParam param) {
        System.out.println(param.numberOfDoors + param.numberOfTrailers);
    }
}

So question is - which of this 3 options is the best one (or if there is some other option I have not found)? In terms of further maintenance, changing etc.

Aucun commentaire:

Enregistrer un commentaire