简体   繁体   中英

Best way to avoid explicit casts

I have a class hierarchy like below

Vehicle
|_ TransaportationVehicle has method getLoadCapacity
|_ PassengerVehicle has method getPassengerCapacity

and I have one more class Booking it have a reference to Vehicle .

Now whenever I have to call getPassengerCapacity or getLoadCapacity on vehicle reference I need to type cast vehicle to its concrete implementation like ((PassengerVehicle)vehicle).getPassengerCapacity() and this type of calls spans over multiple parts in the project. So is there any way with which I can avoid these type of casts and my code will look beautiful and clean?

Note: These are not actual classes I have taken these as an example to demonstrate current problem.

Obviously, when booking a Vehicle you need to distinguish at some point whether it's a TransportationVehicle or a PassengerVehicle as both have different properties.

The easiest way would be to initiate two different Booking processes: one for vehicles that can transport goods, and one for vehicles that can transport passengers. As for how to differentiate between these two types of vehicles: you could add canTransportPassengers() and canTransportGoods() methods to Vehicle , the subclasses would then override these methods to return true where appropriate. Also, this way a vehicle that can transport both is possible, like a train.

If You want to use different method names then You must cast to concrete class.

But if You can make this methods return same type value and have same names You can use polymorphism for it. Create abstract method in Vehicle class and override it in each child.

A quick way I would accomplish this is to create a Generified Booking parent class.

public abstract class Booking<V extends Vehicle> {

    protected abstract V getVehicle();
}

public class TransportationVehicleBooking extends Booking<TransaportationVehicle> {

    @Override
    protected TransaportationVehicle getVehicle() {
        return new TransaportationVehicle();
    }
}

public class PassengerVehicleBooking extends Booking<PassengerVehicle> {

    @Override
    protected PassengerVehicle getVehicle() {
        return new PassengerVehicle();
    }
}

Your Booking class will have all the logic that spans all the booking subclasses and some abstract method each subclasses will need to do effective calculations.

Then all you have to do is have reference to a Booking class and calling the relevant method required without having to worry about the "logistics" (get it) of the booking itself.

I hope this helps.

You method overriding concepts. You need to have all these method in the Parent class and same can be overriden in the child clasees.

You can then access all the methods from super class using Runtime polymorphism

Vehicle

public interface Vehicle {
    public int getCapacity();
}

TransaportationVehicle

public class TransaportationVehicle implements Vehicle {

    @Override
    public int getCapacity() {
        return getLoadCapacity();
    }

    private int getLoadCapacity() {
        return 0;
    }

}

PassengerVehicle

public class PassengerVehicle implements Vehicle {

    @Override
    public int getCapacity() {
        return getPassengerCapacity();
    }

    private int getPassengerCapacity() {
        return 0;
    }
}

USAGE

Vehicle passenger = new PassengerVehicle();
passenger.getCapacity();

Vehicle transaportation = new TransaportationVehicle();
transaportation.getCapacity()

Assuming that passenger capacity is always an integer and load capacity could very well a big number depending on what is the unit for load. I would go ahead and create Vehicle class as follow:

class Vehicle {
    Number capacity; 
    public Number getCapacity() {
        return capacity; 
    }
    public void setCapacity(Number capacity) {
        this.capacity = capacity; 
    }
}

The reason I am using Number is so that I then use Integer in PassengerVehicle class and Double in TransporatationVehicle and that is because Integer and Double are subtype of Number and you can get away with a cast.

class TransportationVehicle extends Vehicle {
    @Override 
    public Double getCapacity() {
        //all I have to do is cast Number to Double
        return (Double) capacity; 
    }

    @Override 
    public void setCapacity(Number capacity) {
        this.capacity = capacity; 
    }
}

Similarly the PassengerVehicle class as follow:

class PassengerVehicle extends Vehicle {
    @Override 
    public Integer getCapacity() {
        //Cast to Integer and works because Integer is subtype of Number
        return (Integer) capacity; 
    }

    @Override 
    public void setCapacity(Number capacity) {
        this.capacity = capacity; 
    }
}

You can then use above classes to create vehicle object as follow:

public class Booking {
    public static void main(String[] args) {
        //
        Vehicle transportationVehicle = new TransportationVehicle();
        //assigning Double to setCapacity
        transportationVehicle.setCapacity(new Double(225.12));


        Vehicle passengerVehicle = new PassengerVehicle(); 
        //assigning Integer to setCapacity
        passengerVehicle.setCapacity(5);

        System.out.println(transportationVehicle.getCapacity()); 
        // output: 225.12
        System.out.println(passengerVehicle.getCapacity());
        // output: 5
    }
}

On the side notes if you try to pass TransportationVehicle anything but Number or Double then you will get Exception and similarly if you pass PassengerVehicle anything but Number or Integer you will get exception.

I know that I am deviating from the scope of your question but, I really want to show how you can make your methods generics. This allow you to decide to return type of getCapacity() during coding which is very flexible. See below:

class Vehicle<T> {
    //generic type T
    T capacity; 

    //generic method getCapacity
    public T getCapacity() {
        return capacity; 
    }

    //generic method setCapacity 
    public void setCapacity(T capacity) {
        this.capacity = capacity; 
    }
}

class TransportationVehicle<T> extends Vehicle<T> {
    @Override 
    public T getCapacity() {
        return capacity; 
    }

    @Override 
    public void setCapacity(T capacity) {
        this.capacity = capacity; 
    }
}

class PassengerVehicle<T> extends Vehicle<T> {
    @Override 
    public T getCapacity() {
        return capacity; 
    }

    @Override 
    public void setCapacity(T capacity) {
        this.capacity = capacity; 
    }
}

As you can see above the generic methods and you can use them as follow:

Vehicle<String> vehicleString = new TransportationVehicle<String>();
vehicleString.setCapacity("Seriously!"); //no problem

Vehicle<Integer> vehicleInteger = new PassengerVehicle<Integer>(); 
vehicleInteger.setCapacity(3); //boxing done automatically

Vehicle<Double> vehicleDouble = new PassengerVehicle<Double>(); 
vehicleDouble.setCapacity(2.2); //boxing done automatically  

You can decide the type while coding and if you supply a Vehicle<String> with capacity as Integer then you will get compile time error, so you won't be allowed.

System.out.println(vehicleString.getCapacity()); 
//output: Seriously!
System.out.println(vehicleInteger.getCapacity());
//output:  3 
System.out.println(vehicleDouble.getCapacity());
//output: 2.2 

First try to extract an abstract method suitable for all vehicles. If you can't do this you can also use an often forgotten pattern - the visitor pattern . Eg

Introduce a visitor interface

public interface VehicleVisitor {
    public void visit(TransportationVehicle transportationVehicle);
    public void visit(PassengerVehicle passengerVehicle);
}

add an accept method to the Vehicle

public interface Vehicle {
    public void accept(VehicleVisitor visitor);
}

implement the accept method in the sub classes

public class PassengerVehicle implements Vehicle {

    private int passengerCapacity;

    public static PassengerVehicle withPassengerCapacity(int passengerCapacity) {
        return new PassengerVehicle(passengerCapacity);

    }

    private PassengerVehicle(int passengerCapacity) {
        this.passengerCapacity = passengerCapacity;
    }

    public int getPassengerCapacity() {
        return passengerCapacity;
    }

    @Override
    public void accept(VehicleVisitor visitor) {
        visitor.visit(this);

    }
}

public class TransportationVehicle implements Vehicle {

    private int loadCapacity;

    public static TransportationVehicle withLoadCapacity(int loadCapacity) {
        return new TransportationVehicle(loadCapacity);

    }

    private TransportationVehicle(int loadCapacity) {
        this.loadCapacity = loadCapacity;
    }

    public int getLoadCapacity() {
        return loadCapacity;
    }

    @Override
    public void accept(VehicleVisitor visitor) {
        visitor.visit(this);
    }
}

implement a visitor...

public class LoadSupported implements VehicleVisitor {

    private boolean supported;
    private int load;

    public LoadSupported(int load) {
        this.load = load;
    }

    public boolean isSupported() {
        return supported;
    }

    @Override
    public void visit(TransportationVehicle transportationVehicle) {
        int loadCapacity = transportationVehicle.getLoadCapacity();
        supported = load <= loadCapacity;

    }

    @Override
    public void visit(PassengerVehicle passengerVehicle) {
        supported = false;
    }

}

...and use it

public class Main {

    public static void main(String[] args) {
        TransportationVehicle transportationVehicle1 = TransportationVehicle
                .withLoadCapacity(5);
        TransportationVehicle transportationVehicle2 = TransportationVehicle
                .withLoadCapacity(10);
        PassengerVehicle passengerVehicle = PassengerVehicle
                .withPassengerCapacity(5);

        LoadSupported loadSupported = new LoadSupported(7);

        supportsLoad(transportationVehicle1, loadSupported);
        supportsLoad(transportationVehicle2, loadSupported);
        supportsLoad(passengerVehicle, loadSupported);

    }

    private static void supportsLoad(Vehicle vehicle,
            LoadSupported loadSupported) {
        vehicle.accept(loadSupported);
        System.out.println(vehicle.getClass().getSimpleName() + "[" + System.identityHashCode(vehicle) + "]" + " does"
                + (loadSupported.isSupported() ? " " : " not ")
                + "support load capacity");
    }
}

The output will be something like this

TransportationVehicle[778966024] does not support load capacity
TransportationVehicle[1021653256] does support load capacity
PassengerVehicle[1794515827] does not support load capacity

I don't understand the example. How do you realize that you are dealing with a concrete type in the first place? Are you instanceOf-ing? Are you type matching?

If so your problem is way past casting...

Anyways when you have objects that must belong to the same family and algorithms which are not abstract and change according to the object being handled you typically use some sort of behavioral pattern like visitor, or the Bridge pattern .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM