如何在实施工厂设计模式时避免"instanceof"?

Cha*_*mes 37 java design-patterns factory instanceof factory-pattern

我正在尝试实现我的第一个工厂设计模式,我不确定在将工厂制造的对象添加到列表时如何避免使用instanceof.这就是我想要做的:

for (ABluePrint bp : bluePrints) {
    AVehicle v = AVehicleFactory.buildVehicle(bp);
    allVehicles.add(v);

    // Can I accomplish this without using 'instanceof'?
    if (v instanceof ACar) {
        cars.add((ACar) v);
    } else if (v instanceof ABoat) {
        boats.add((ABoat) v);
    } else if (v instanceof APlane) {
        planes.add((APlane) v);
    }
}
Run Code Online (Sandbox Code Playgroud)

从我在SO上看到的,使用'instanceof'是代码气味.有没有更好的方法来检查工厂创建的车辆类型而不使用'instanceof'?

我欢迎任何关于我的实施的反馈/建议,因为我不确定我是否正确地采用了这种方式.

完整示例如下:

import java.util.ArrayList;

class VehicleManager {

    public static void main(String[] args) {

        ArrayList<ABluePrint> bluePrints = new ArrayList<ABluePrint>();
        ArrayList<AVehicle> allVehicles = new ArrayList<AVehicle>();
        ArrayList<ACar> cars = new ArrayList<ACar>();
        ArrayList<ABoat> boats = new ArrayList<ABoat>();
        ArrayList<APlane> planes = new ArrayList<APlane>();

        /*
        *  In my application I have to access the blueprints through an API
        *  b/c they have already been created and stored in a data file.
        *  I'm creating them here just for example.
        */
        ABluePrint bp0 = new ABluePrint(0);
        ABluePrint bp1 = new ABluePrint(1);
        ABluePrint bp2 = new ABluePrint(2);
        bluePrints.add(bp0);
        bluePrints.add(bp1);
        bluePrints.add(bp2);

        for (ABluePrint bp : bluePrints) {
            AVehicle v = AVehicleFactory.buildVehicle(bp);
            allVehicles.add(v);

            // Can I accomplish this without using 'instanceof'?
            if (v instanceof ACar) {
                cars.add((ACar) v);
            } else if (v instanceof ABoat) {
                boats.add((ABoat) v);
            } else if (v instanceof APlane) {
                planes.add((APlane) v);
            }
        }

        System.out.println("All Vehicles:");
        for (AVehicle v : allVehicles) {
            System.out.println("Vehicle: " + v + ", maxSpeed: " + v.maxSpeed);
        }

        System.out.println("Cars:");
        for (ACar c : cars) {
            System.out.println("Car: " + c + ", numCylinders: " + c.numCylinders);
        }

        System.out.println("Boats:");
        for (ABoat b : boats) {
            System.out.println("Boat: " + b + ", numRudders: " + b.numRudders);
        }

        System.out.println("Planes:");
        for (APlane p : planes) {
            System.out.println("Plane: " + p + ", numPropellers: " + p.numPropellers);
        }
    }
}

class AVehicle {

    double maxSpeed;

    AVehicle(double maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
}

class ACar extends AVehicle {

    int numCylinders;

    ACar(double maxSpeed, int numCylinders) {
        super(maxSpeed);
        this.numCylinders = numCylinders;
    }
}

class ABoat extends AVehicle {

    int numRudders;

    ABoat(double maxSpeed, int numRudders) {
        super(maxSpeed);
        this.numRudders = numRudders;
    }
}

class APlane extends AVehicle {

    int numPropellers;

    APlane(double maxSpeed, int numPropellers) {
        super(maxSpeed);
        this.numPropellers = numPropellers;
    }
}

class AVehicleFactory {

    public static AVehicle buildVehicle(ABluePrint blueprint) {

        switch (blueprint.type) {

            case 0:
                return new ACar(100.0, 4);

            case 1:
                return new ABoat(65.0, 1);

            case 2:
                return new APlane(600.0, 2);

            default:
                return new AVehicle(0.0);
        }
    }
}

class ABluePrint {

    int type; // 0 = car; // 1 = boat; // 2 = plane;

    ABluePrint(int type) {
        this.type = type;
    }
}
Run Code Online (Sandbox Code Playgroud)

Vin*_*igh 67

您可以实现访问者模式.


详细解答

我们的想法是使用多态来执行类型检查.每个子类都会覆盖该accept(Visitor)方法,该方法应在超类中声明.当我们遇到这样的情况时:

void add(Vehicle vehicle) {
    //what type is vehicle??
}
Run Code Online (Sandbox Code Playgroud)

我们可以将一个对象传递给一个声明的方法Vehicle.如果vehicle是类型Car,并class Car覆盖我们传递对象的方法,那么该对象现在将在Car类中声明的方法内处理.我们使用它有利于我们:创建一个Visitor对象并将其传递给overriden方法:

abstract class Vehicle {
    public abstract void accept(AddToListVisitor visitor);
}

class Car extends Vehicle {
    public void accept(AddToListVisitor visitor) {
        //gets handled in this class
    }
}
Run Code Online (Sandbox Code Playgroud)

这Visitor应该准备访问类型Car.instanceof必须在中指定要避免用于查找实际类型的任何类型Visitor.

class AddToListVisitor {
    public void visit(Car car) {
        //now we know the type! do something...
    }

    public void visit(Plane plane) {
        //now we know the type! do something...
    }
}
Run Code Online (Sandbox Code Playgroud)

这是类型检查发生的地方!

当Car收到访问者时,它应该使用this关键字传递给自己.由于我们在课堂上Car,因此visit(Car)将调用该方法.在访问者内部,我们可以执行我们想要的操作,现在我们知道了对象的类型.


所以,从顶部:

您创建一个Visitor,执行您想要的操作.访问者应该包含visit要对其执行操作的每种类型对象的方法.在这种情况下,我们正在为车辆创建访客:

interface VehicleVisitor {
    void visit(Car car);
    void visit(Plane plane);
    void visit(Boat boat);
}
Run Code Online (Sandbox Code Playgroud)

我们想要执行的操作是将车辆添加到某物.我们会创造一个AddTransportVisitor; 管理添加运输的访客:

class AddTransportVisitor implements VehicleVisitor {
    public void visit(Car car) {
        //add to car list
    }

    public void visit(Plane plane) {
        //add to plane list
    }

    public void visit(Boat boat) {
        //add to boat list
    }
}
Run Code Online (Sandbox Code Playgroud)

每辆车都应该能够接受车辆访客:

abstract class Vehicle {
    public abstract void accept(VehicleVisitor visitor);
}
Run Code Online (Sandbox Code Playgroud)

当访客被传递到车辆时,车辆应该调用它的visit方法,将自己传递给参数:

class Car extends Vehicle {
    public void accept(VehicleVisitor visitor) {
        visitor.visit(this);
    }
}

class Boat extends Vehicle {
    public void accept(VehicleVisitor visitor) {
        visitor.visit(this);
    }
}

class Plane extends Vehicle {
    public void accept(VehicleVisitor visitor) {
        visitor.visit(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是类型检查发生的地方.visit调用正确的方法,该方法包含基于方法参数执行的正确代码.

最后一个问题是VehicleVisitor与列表进行交互.这就是您的VehicleManager用武之地:它封装了列表,允许您通过VehicleManager#add(Vehicle)方法添加车辆.

当我们创建访问者时,我们可以将管理器传递给它(可能通过它的构造函数),这样我们就可以执行我们想要的操作,因为我们知道了对象的类型.本VehicleManager应包含的游客和拦截VehicleManager#add(Vehicle)来电:

class VehicleManager {
    private List<Car> carList = new ArrayList<>();
    private List<Boat> boatList = new ArrayList<>();
    private List<Plane> planeList = new ArrayList<>();

    private AddTransportVisitor addVisitor = new AddTransportVisitor(this);

    public void add(Vehicle vehicle) {
        vehicle.accept(addVisitor);
    }

    public List<Car> getCarList() {
        return carList;
    }

    public List<Boat> getBoatList() {
        return boatList;
    }

    public List<Plane> getPlaneList() {
        return planeList;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们现在可以编写AddTransportVisitor#visit方法的实现:

class AddTransportVisitor implements VehicleVisitor {
    private VehicleManager manager;

    public AddTransportVisitor(VehicleManager manager) {
        this.manager = manager;
    }

    public void visit(Car car) {
        manager.getCarList().add(car);
    }

    public void visit(Plane plane) {
        manager.getPlaneList().add(plane);
    }

    public void visit(Boat boat) {
       manager.getBoatList().add(boat);
    }
}
Run Code Online (Sandbox Code Playgroud)

我强烈建议删除getter方法并add为每种类型的车辆声明重载方法.这将减少不需要时"访问"的开销,例如manager.add(new Car()):

class VehicleManager {
    private List<Car> carList = new ArrayList<>();
    private List<Boat> boatList = new ArrayList<>();
    private List<Plane> planeList = new ArrayList<>();

    private AddTransportVisitor addVisitor = new AddTransportVisitor(this);

    public void add(Vehicle vehicle) {
        vehicle.accept(addVisitor);
    }

    public void add(Car car) {
        carList.add(car);
    }

    public void add(Boat boat) {
        boatList.add(boat);
    }

    public void add(Plane plane) {
        planeList.add(plane);
    }

    public void printAllVehicles() {
        //loop through vehicles, print
    }
}

class AddTransportVisitor implements VehicleVisitor {
    private VehicleManager manager;

    public AddTransportVisitor(VehicleManager manager) {
        this.manager = manager;
    }

    public void visit(Car car) {
        manager.add(car);
    }

    public void visit(Plane plane) {
        manager.add(plane);
    }

    public void visit(Boat boat) {
       manager.add(boat);
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle[] vehicles = {
            new Plane(),
            new Car(),
            new Car(),
            new Car(),
            new Boat(),
            new Boat()
        };

        VehicleManager manager = new VehicleManager();
            for(Vehicle vehicle : vehicles) {
                manager.add(vehicle);
            }

            manager.printAllVehicles();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 虽然这有效,但我认为这种"解决方案"要比他试图解决的问题更糟糕. (14认同)
  • @LorenPechtel,访客是最优雅的解决方案,即使它对于未经训练的眼睛看起来很奇怪.它允许将单个调度转换为多个调度.基本理念是,"不要打电话给我们,我们会打电话给你." (13认同)
  • @LorenPechtel"车辆"*不会将自己添加到列表中.访客进行添加.`Vehicle`实例不知道`VehicleManager` (5认同)
  • 这正是我一直在寻找的那种解决方案.我知道它可以做得更好,但我只是不确定如何.非常感谢您花时间详细解释这一点!我非常感激. (4认同)
  • @LorenPechtel如果你详细说明它会如何变得更糟,那将会很好. (2认同)
  • 访问者模式非常类似于`if(... instanceof ...)`链,除非你错过任何案例(好)你会得到一个错误,并且它需要更多的样板(坏). (2认同)
  • @immibis他们都给出相同的结果:检查类型,是的,访问者模式需要更多的代码.但是`instanceof`也存在问题(向下转换,没有模块化,由于条件条件而导致执行速度提高),所以总的来说,它取决于开发和情况. (2认同)
  • @LorenPechtel`instageof`链没有捕获错误; 由于不包括条件,不会抛出异常.是的,如果你在不检查该对象是否是特定类型的实例的情况下尝试进行转换,那么你将得到一个`ClassCastException`,你可以考虑"错误检查".但是在运行时会被告知错误,而使用Visitor模式,如果您尝试调用`visitor.visit(this)`并且访问者未指定您正在使用的类型,则会出现编译器错误. (2认同)