具有可变参数类型的接口方法

时间:2017-02-18 15:02:11

标签: java oop generics design-patterns solid-principles

我有java接口和类实现,在调用类似的行为时需要不同的参数。以下哪项最适合?

在第一个选项中,我有不同的类从基接口继承常见行为,所有差异只在类中直接实现,而不是在接口中实现。这个似乎最合适,但我必须在代码中进行手动类型转换。

public class VaryParam1 {

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

    static List<Car> carsList = new ArrayList<>();
    static List<TruckWithTrailer> trucksList = new ArrayList<>();

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

        //violates LSP?
        ((Car)list.get(VehicleType.WITHOUT_TRAILER)).paint(1); //ok - but needed manual cast
        ((TruckWithTrailer)list.get(VehicleType.WITH_TRAILER)).paint(1, "1"); //ok - but needed manual cast

        carsList.add(new Car());
        trucksList.add(new TruckWithTrailer());

        //Does not violate LSP
        carsList.get(0).paint(1);
        trucksList.get(0).paint(1, "1");
    }
}

enum VehicleType {
    WITHOUT_TRAILER,
    WITH_TRAILER;
}

interface Vehicle{
    //definition of all common methods
    void drive();
    void stop();
}

class Car implements Vehicle {

    public void paint(int vehicleColor) {
        System.out.println(vehicleColor);
    }

    @Override
    public void drive() {}

    @Override
    public void stop() {}
}

class TruckWithTrailer implements Vehicle {

    public void paint(int vehicleColor, String trailerColor) {
        System.out.println(vehicleColor + trailerColor);
    }

    @Override
    public void drive() {}

    @Override
    public void stop() {}
}

在第二个选项中,我已将方法一级移动到接口,但现在我需要使用UnsupportedOpException实现行为。这看起来像代码味道。在代码中,我不必进行手动转换,但我也可以调用在运行时产生异常的方法 - 无需编译时检查。这不是一个大问题 - 只有这种方法有异常,看起来像代码味道。这种实施方式是最佳实践吗?

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).paint(1); //works
        list.get(VehicleType.WITH_TRAILER).paint(1, "1"); //works

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

enum VehicleType {
    WITHOUT_TRAILER,
    WITH_TRAILER;
}

interface Vehicle{
    void paint(int vehicleColor);
    void paint(int vehicleColor, String trailerColor);    //code smell - not valid for all vehicles??
}

class Car implements Vehicle {

    @Override
    public void paint(int vehicleColor) {
        System.out.println(vehicleColor);
    }

    @Override
    public void paint(int vehicleColor, String trailerColor) {    //code smell ??
        throw new UnsupportedOperationException("Car has no trailer");
    }
}

class TruckWithTrailer implements Vehicle {

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

    @Override
    public void paint(int vehicleColor, String trailerColor) {
        System.out.println(vehicleColor + trailerColor);
    }
}

这里我使用泛型以便在接口中使用通用方法,并且在每个类实现中决定参数类型。这里的问题是我有未经检查的绘画调用。这与选项1中的直接投射问题类似。在这里我也有可能调用我不应该的方法!

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).paint(new VehicleParam());    //works but unchecked call
        list.get(VehicleType.WITH_TRAILER).paint(new TruckWithTrailerParam());    //works but unchecked call

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

enum VehicleType {
    WITHOUT_TRAILER,
    WITH_TRAILER;
}

class VehicleParam {
    int vehicleColor;
}

class TruckWithTrailerParam extends VehicleParam {
    String trailerColor;
}

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

class Car implements Vehicle<VehicleParam> {

    @Override
    public void paint(VehicleParam param) {
        System.out.println(param.vehicleColor);
    }
}

class TruckWithTrailer implements Vehicle<TruckWithTrailerParam> {

    @Override
    public void paint(TruckWithTrailerParam param) {
        System.out.println(param.vehicleColor + param.trailerColor);
    }
}

所以问题是 - 这3个选项中哪一个是最好的(或者如果还有其他选项我还没找到)?在进一步维护,改变等方面。

更新

我更新了问题,现在我有了paint方法,只有在构造了对象后才可以调用它。

到目前为止,这看起来是最好的选择,因为它在下面的帖子中提出:

public class VaryParam4 {

    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).paint(new PaintConfigObject());    //works but can pass trailerColor (even if null) that is not needed
        list.get(VehicleType.WITH_TRAILER).paint(new PaintConfigObject());    //works
    }
}

enum VehicleType {
    WITHOUT_TRAILER,
    WITH_TRAILER;
}

class PaintConfigObject {
    int vehicleColor;
    String trailerColor;
}

interface Vehicle{
    void paint(PaintConfigObject param);
}

class Car implements Vehicle {

    @Override
    public void paint(PaintConfigObject param) {
        //param.trailerColor will never be used here but it's passed in param
        System.out.println(param.vehicleColor);
    }
}

class TruckWithTrailer implements Vehicle {

    @Override
    public void paint(PaintConfigObject param) {
        System.out.println(param.vehicleColor + param.trailerColor);
    }
}

2 个答案:

答案 0 :(得分:8)

更好的选择是删除drive方法的重载版本,并传递构造函数中子类所需的任何信息:

interface Vehicle{
    void drive();
}

class Car implements Vehicle {
    private int numberOfDoors;

    public Car(int numberOfDoors) {
         this.numberOfDoors = numberOfDoors;
     }

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


class TruckWithTrailer implements Vehicle {
    private int numberOfDoors;
    private int numberOfTrailers;

    public TruckWithTrailer(int numberOfDoors,numberOfTrailers) {
          this.numberOfDoors = numberOfDoors;
          this.numberOfTrailers = numberOfTrailers;
    }

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

解决有关在运行时决定的paint的评论,您可以向带有可变参数的车辆添加paint方法:

interface Vehicle{
    void drive();
    void paint(String ...colors);
}

正如评论中所讨论的,如果paint方法中使用的参数数量因不同的车辆类型而异,请定义一个名为PaintSpecification的类,其中包含vehcileColor,{{1}等属性并更改trailerColor方法以使参数类型为paint

PaintSpecification

上述所有方法的优势在于,所有interface Vehicle{ void drive(); void paint(PaintSpecification spec); } 实施都遵循单一合同,允许您执行操作,例如将所有Vehicle个实例添加到Vehicle并调用无论其类型如何,都会逐一List方法。

答案 1 :(得分:1)

  

但我必须在代码中进行手动类型转换。

这是因为你丢失了你显然需要的类型信息。

您的客户端代码取决于具体的类型信息,因为您的绘制方法取决于具体类型。

如果您的客户端代码不应该知道具体的Vehicle类型,那么Vehicle接口应该以不需要具体类型信息的方式设计。 E.g。

public void paint();

这也意味着每个Vehicle实例必须拥有绘制自身所需的所有信息。因此,您应该实现 color属性

public class Car implements Vehicle {

  private int color = 0; // Default color

  public void paint() {
    System.out.println(color);
  }

  public void setColor(int color){
     // maybe some validation first
     this.color = color;
  }
}

你还能做什么?

如果您希望保持代码不变,则必须以某种方式重新创建类型信息。

我看到以下解决方案:

  • instanceof 使用向下转发检查(您已尝试过)
  • 适配器点模式
  • 访问者点模式

<强>适配器点模式

interface Vehicle {
    public <T extends Vehicle> T getAdapter(Class<T> adapterClass);
}

class Car implements Vehicle {

    @Override
    public <T extends Vehicle> T getAdapter(Class<T> adapterClass) {
        if(adapterClass.isInstance(this)){
            return adapterClass.cast(this);
        }
        return null;
    }
}

您的客户端代码将如下所示:

Vehicle vehicle = ...;

Car car = vehicle.getAdapter(Car.class);
if(car != null){
    // the vehicle can be adapted to a car
    car.paint(1);
}

适配器模式的优点

  • 您将instanceof检查从客户端代码移动到适配器中。因此,客户端代码将更加重构安全。例如。想象一下以下客户端代码:

    if(vehicle instanceof Car){
       // ...
    } else if(vehicle instanceof TruckWithTrailer){
       // ...
    }
    

    想一想如果将代码重构为TruckWithTrailer extends Car

  • 会发生什么
  • 适配器不得自行返回。具体的Vehicle可以实例化另一个让它看起来像适配器类型的对象。

    public <T extends Vehicle> T getAdapter(Class<T> adapterClass) {
        if(Car.class.isAssignableFrom(adapterClass)){
            return new CarAdapter(this)
        }
        return null;
    }
    

适配器模式的缺点

  • 当您添加越来越多的Vehicle(很多if-else语句)实现时,cyclomatic complexity客户端代码会增加。

<强>观众点模式

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

interface VehicleVisitor {
    public void visit(Car car);
    public void visit(TruckWithTrailer truckWithTrailer);
}

然后汽车的实现将决定应该调用VihicleVisitor的哪个方法。

class Car implements Vehicle {

    public void paint(int vehicleColor) {
        System.out.println(vehicleColor);
    }

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

您的客户端代码必须提供VehicleVisitor

    Vehicle vehicle = ...;
    vehicle.accept(new VehicleVisitor() {

        public void visit(TruckWithTrailer truckWithTrailer) {
            truckWithTrailer.paint(1, "1");

        }

        public void visit(Car car) {
            car.paint(1);
        }
    });

访客模式的优点

  • 在单独的方法中分离特定类型的逻辑

访客模式的缺点

  • 新类型需要更改访问者界面,并且还必须更改访问者的所有实现。

PS:有关代码上下文的更多信息,可能还有其他解决方案。