Home > Mobile >  How to implement a generic "options" parameter?
How to implement a generic "options" parameter?

Time:10-25

I have a factory that returns objects:

 public class Factory {

        public static Vehicle getVehicle(Options options) {
            if (options.getType().equals("car")) return new Car(options);
            if (options.getType().equals("moped")) return new Moped(options);

        }
    }

The objects that the factory creates should be able to take class specific options as parameters:

public class Car extends Vehicle {
    public Car(final CarOptions carOptions) { this.carOptions = carOptions; }
}
public class Moped extends Vehicle {
    public Moped(final MopedOptions mopedOptions) { this.mopedOptions = mopedOptions; }
}

The options classes:

public class Options {
    // methods
}
public class CarOptions implements Options {
    // methods
}
public class Moped implements Options {
    // methods
}

How can I give the Factory.getVehicle method a generic Options parameter and have it passed on to the objects the factory creates? I can call Factory.getVehicle(new CarOptions()) just fine but inside the factory itself the objects expect a specific type of Options.

Required type: CarOptions Provided: Options

I thought that because CarOptions implements Options I could just pass it on as a parameter to new Car(options) but that does not seem to be the case.

I've tried to resolve this by using generics but haven't found a solution that works.

CodePudding user response:

To give Factory.getVehicle generic Options this interface must be generic: Options<V extends Vehicle>. Now your implementations can setup their specification:

CarOptions implements Options<Car>
MopedOptions implements Options<Moped>

Since your Car/Moped constructor needs the specific Optionsimplementation you have to cast the parameter in getVehicle. For the generic return value you have to cast as well:

public static <V extends Vehicle> V getVehicle(Options<V> options) {
    if (options.getType().equals("car")) { // or options instanceof CarOptions?
        return (V) new Car((CarOptions) options);
    }
    if (options.getType().equals("moped")) { // or options instanceof MopedOptions?
        return (V) new Moped((MopedOptions) options);
    }
    return null;
}

EDIT: visitor pattern

To get rid of these casts you could use the visitor pattern. Therefor you have to change your factory. Note that the methods must not be static. And if you get some more vehicles you have to add a method for it.

public Car visit(CarOptions options) {
    return new Car(options);
}

public Moped visit(MopedOptions options) {
    return new Moped(options);
}

You also have to add the abstract method V visit(Factory visitor); to interface Options<V extends Vehicle>. The implementation is that simple:

public class CarOptions implements Options<Car> {
    // other methods
    @Override
    public Car visit(Factory visitor) {
        return visitor.visit(this);
    }
}

public class MopedOptions implements Options<Moped> {
    // other methods
    @Override
    public Moped visit(Factory visitor) {
        return visitor.visit(this);
    }
}

With this pattern there is no need to check getType() or instanceof. You can simply call Car car = new CarOptions().visit(new Factory());

CodePudding user response:

There's countless ways to achieve this, but it resembles homework so I'll stick with the quick (not the best) solution.

The first thing is to either make your CarOptions and MopedOptions classes either extend the Options class, or make theOptions class an interface, I did the latter.

Then I have setup Vehicle and Moped up to accept the Options interface. Here you didn't have class fields for the options properties.

I also changed the way you were trying to fetch the class details in the getVehicle() method to compare the class name against the options class name.

Generics (e.g. class Clazz) would be a much better way of doing this, but I suspect you'll get to generics after you're done with polymorphism and inheritance

  • Related