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

时间:2015-04-05 15:07:50

标签: java design-patterns factory factory-pattern instanceof

我正在尝试实现我的第一个工厂设计模式,并且我不确定在将工厂制造的对象添加到列表时如何避免使用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);
    }
}

从我在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;
    }
}

7 个答案:

答案 0 :(得分:65)

您可以实施Visitor pattern


详细答案

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

void add(Vehicle vehicle) {
    //what type is vehicle??
}

我们可以将对象传递给Vehicle中声明的方法。如果vehicle的类型为Car,并且class Car覆盖了我们将对象传入的方法,则该对象现在将在Car类中声明的方法内处理。我们使用它有利于我们:创建一个Visitor对象并将其传递给重写方法:

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

class Car extends Vehicle {
    public void accept(AddToListVisitor visitor) {
        //gets handled in this class
    }
}

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...
    }
}

这是进行类型检查的地方!

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


所以,从顶部开始:

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

interface VehicleVisitor {
    void visit(Car car);
    void visit(Plane plane);
    void visit(Boat boat);
}

我们想要执行的操作是将车辆添加到某物。我们会创建一个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
    }
}

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

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

当访客被传递到车辆时,车辆应该调用它的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);
    }
}

这就是进行类型检查的地方。调用正确的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;
    }
}

我们现在可以编写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);
    }
}

我强烈建议删除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();
    }
}

答案 1 :(得分:2)

您可以向车辆类添加方法以打印文本。然后覆盖每个专用Car类中的方法。然后将所有车辆添加到车辆列表中。并循环列表以打印文本。

答案 2 :(得分:2)

完成一些代码重组。希望对你有用。检查一下:

    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 = null;
            ArrayList<ABoat> boats = null;
            ArrayList<APlane> planes = null;

            /*
            *  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'?

                // dont add objects to list here, do it from constructor or in factory
                /*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);
                }*/
            }

            cars = ACar.getCars();
            boats = ABoat.getBoats();
            planes = APlane.getPlanes();

            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;
        }

        void add(){}
    }

    class ACar extends AVehicle {

        static ArrayList<ACar> cars = new ArrayList<ACar>();
        int numCylinders;

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

        void add(){
            cars.add(this);
        }

        public static ArrayList<ACar> getCars(){
            return cars;
        }
    }

    class ABoat extends AVehicle {

        static ArrayList<ABoat> boats = new ArrayList<ABoat>();
        int numRudders;

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

        void add(){
            boats.add(this);
        }

        public static ArrayList<ABoat> getBoats(){
            return boats;
        }
    }

    class APlane extends AVehicle {

        static ArrayList<APlane> planes = new ArrayList<APlane>();
        int numPropellers;

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

        void add(){
            planes.add(this);
        }

        public static ArrayList<APlane> getPlanes(){
            return planes;
        }
    }

    class AVehicleFactory {

        public static AVehicle buildVehicle(ABluePrint blueprint) {

            AVehicle vehicle;

            switch (blueprint.type) {

                case 0:
                    vehicle = new ACar(100.0, 4);
                    break;

                case 1:
                    vehicle = new ABoat(65.0, 1);
                    break;

                case 2:
                    vehicle = new APlane(600.0, 2);
                    break;

                default:
                    vehicle = new AVehicle(0.0);
            }

            vehicle.add();
            return vehicle;
        }
    }

    class ABluePrint {

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

        ABluePrint(int type) {
            this.type = type;
        }
    }

使用上面的代码,该类必须知道必须添加它的集合。这可以被认为是良好设计的缺点,可以使用访客设计模式来克服,如接受的答案(How to avoid 'instanceof' when implementing factory design pattern?)所示。

答案 3 :(得分:2)

我对汽车,船只和飞机的名单一开始并不太满意。你有多个现实的例子,但这个列表本身并不是包罗万象的 - 当你的工厂开始制造潜艇或火箭时会发生什么?

相反,如何使用汽车,船和飞机类型的枚举。你有一系列车辆列表。

通用车辆具有抽象属性CatalogAs,各种车辆实际上实现了这一点并返回正确的值。

答案 4 :(得分:1)

我知道问这个问题已经很久了。我发现 http://www.nurkiewicz.com/2013/09/instanceof-operator-and-visitor-pattern.html 看起来很有用。如果有人有兴趣,请在这里分享。

答案 5 :(得分:0)

有一个类似的问题,所以我使用这个模式,为了更好地理解它,我创建了一个简单的UML绘图,显示了注释中的事物序列(按照数字)。我上面使用了Vince Emighs解决方案..模式解决方案更优雅,但可能需要一些时间来真正理解。它需要一个接口和一个类比原始接口更多,但它们非常简单。

the original is on the right side, the solution using the visitor pattern is on the left side

答案 6 :(得分:0)

如果AVehicle类超出您的控制范围怎么办?例如。你有来自第三党的库吗?因此,您无法添加访问者模式的accept()方法。同样,您可能不喜欢每个AVehicle子类中的样板代码,而是更喜欢将所有内容放在一个特殊的类中,以保持类的整洁。 在某些情况下,最好只使用HashMap。

在您的示例中,只需使用:

Map<Class<? extends AVehicle>, List<? extends AVehicle>> lists = new HashMap<>();
lists.put(ACar.class, new ArrayList<ACar>());
lists.put(ABoat.class, new ArrayList<ABoat>());
lists.put(APlane.class, new ArrayList<APlane>());

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

此HashMap方法的问题在于,您必须注册所有可能的类,包括所有已知的子类。尽管如果您具有庞大的层次结构,并且不需要您的任务使用所有类,则可以在地图中保存大量需要的工作。