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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29458676/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 08:03:12  来源:igfitidea点击:

How to avoid 'instanceof' when implementing factory design pattern?

javadesign-patternsfactoryfactory-patterninstanceof

提问by Charlie James

I am attempting to implement my first Factory Design Pattern, and I'm not sure how to avoid using instanceof when adding the factory-made objects to lists. This is what I'm trying to do:

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

From what I've read on SO, using 'instanceof' is a code smell. Is there a better way to check the type of vehicle that was created by the factory without using 'instanceof'?

根据我在 SO 上的阅读,使用 'instanceof' 是一种代码味道。有没有更好的方法来检查工厂创建的车辆类型而不使用“instanceof”?

I welcome any feedback/suggestions on my implementation as I'm not even sure if I'm going about this the right way.

我欢迎对我的实施提出任何反馈/建议,因为我什至不确定我是否以正确的方式解决这个问题。

Full example below:

完整示例如下:

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

采纳答案by Vince Emigh

You could implement the Visitor pattern.

您可以实现访问者模式



Detailed Answer

详细解答

The idea is to use polymorphismto perform the type-checking. Each subclass overrides the accept(Visitor)method, which should be declared in the superclass. When we have a situation like:

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

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

We can pass an object into a method declared in Vehicle. If vehicleis of type Car, and class Caroverrode the method we passed the object into, that object would now be processed within the method declared in the Carclass. We use this to our advantage: creating a Visitorobject and pass it to an overriden method:

我们可以将一个对象传递给 中声明的方法Vehicle。如果vehicle是 type 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
    }
}

This Visitorshould be prepared to visit type Car. Any type that you want to avoid using instanceofto find the actual type of must be specified in the Visitor.

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

Here's where the type checking happens!

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

When the Carreceives the visitor, it should pass itself in using the thiskeyword. Since we are in class Car, the method visit(Car)will be invoked. Inside of our visitor, we can perform the action we want, now that we know the type of the object.

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



So, from the top:

所以,从上往下:

You create a Visitor, which performs the actions you want. A visitor should consist of a visitmethod for each type of object you want to perform an action on. In this case, we are creating a visitor for vehicles:

您创建一个Visitor,它执行您想要的操作。访问者应包含visit您要对其执行操作的每种类型的对象的方法。在本例中,我们为车辆创建了一个访问者:

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

The action we want to perform is adding the vehicle to something. We would create an AddTransportVisitor; a visitor that manages adding transportations:

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

Every vehicle should be able to accept vehicle visitors:

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

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

When a visitor is passed to a vehicle, the vehicle should invoke it's visitmethod, passing itself into the arguments:

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

That's where the type-checking happens. The correct visitmethod is called, which contains the correct code to execute based on the method's parameters.

这就是类型检查发生的地方。visit调用正确的方法,其中包含要根据方法参数执行的正确代码。

The last problem is having the VehicleVisitorinteract with the lists. This is where your VehicleManagercomes in: it encapsulates the lists, allowing you to add vehicles through a VehicleManager#add(Vehicle)method.

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

When we create the visitor, we can pass the manager to it (possibly through it's constructor), so we can perform the action we want, now that we know the object's type. The VehicleManagershould contain the visitor and intercept VehicleManager#add(Vehicle)calls:

当我们创建访问者时,我们可以将管理器传递给它(可能通过它的构造函数),这样我们就可以执行我们想要的操作,现在我们知道对象的类型。本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;
    }
}

We can now write implementations for the AddTransportVisitor#visitmethods:

我们现在可以为这些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);
    }
}

I highly suggest removing the getter methods and declaring overloaded addmethods for each type of vehicle. This will reduce overhead from "visiting" when it's not needed, for example, manager.add(new Car()):

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

回答by Himanshu Ahire

You can add method to vehicle class to print the text. Then override the method in each specialized Car class. Then just add all the cars to the vehicle list. And loop the list to print the text.

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

回答by Anshuman

Done some restructuring of your code. Hope that works for you. Check this:

对您的代码进行了一些重组。希望这对你有用。检查这个:

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

With the above code, the class will have to know about the collection to which it has to be added. This can be considered as a downside to a good design and it can be overcome using the visitor design pattern as demonstrated in the accepted answer (How to avoid 'instanceof' when implementing factory design pattern?).

使用上面的代码,类必须知道它必须添加到的集合。这可以被认为是良好设计的一个缺点,并且可以使用访问者设计模式来克服,如已接受的答案中所示(如何在实现工厂设计模式时避免“instanceof”?)。

回答by Loren Pechtel

I'm not too happy with the lists of cars, boats and planes in the first place. You have multiple examples of reality but the list isn't inherently all-inclusive--what happens when your factory starts making submarines or rockets?

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

Instead, how about an enum with the types car, boat and plane. You have an array of lists of vehicles.

相反,使用类型为 car、boat 和 plane 的枚举如何。您有一系列车辆列表。

The generic vehicle has an abstract property CatalogAs, the various vehicles actually implement this and return the proper value.

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

回答by Orhan

Had a similar issue so I used this pattern, to understand it better I created a simple UML drawing showing the sequence of things in comments (follow the numbers). I used Vince Emighs solution above.. The pattern solution is more elegant but can requires some time to truly understand. It requires one interface and one class more then the original but they are very simple.

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

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

原始在右侧,使用访问者模式的解决方案在左侧

回答by Hari Rao

I know its been a long time since this question was asked. I found http://www.nurkiewicz.com/2013/09/instanceof-operator-and-visitor-pattern.htmlwhich looks to be useful. Sharing it here in case if somebody is interested.

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

回答by engilyin

What if AVehicle classes are out of your control? E.g. you have it from some 3rd party lib? So you have no way to add the Visitor pattern accept() method. Also you could probably dislike boilerplate code in each of the AVehicle subclass and prefer to put everything in one special class keeping your classes clean. For some cases it could be better just to use HashMap.

如果 AVehicle 课程超出您的控制怎么办?例如,您从某些 3rd 方库中获得了它?所以你没有办法添加访问者模式的 accept() 方法。此外,您可能不喜欢每个 AVehicle 子类中的样板代码,而更喜欢将所有内容放在一个特殊的类中,以保持您的类清洁。在某些情况下,使用 HashMap 可能会更好。

In your sample just use:

在您的示例中只需使用:

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

The problem with this HashMap approach is that you have to register all possible classes including all known subclasses. Although if you have huge hierarchy and it is not needed all classes for your task you can save lots of work registering in the Map just needed ones.

这种 HashMap 方法的问题在于您必须注册所有可能的类,包括所有已知的子类。尽管如果您有庞大的层次结构并且您的任务不需要所有类,您可以节省大量工作,只需在 Map 中注册只需要的类即可。