【问题标题】:How to avoid 'instanceof' when implementing factory design pattern?实现工厂设计模式时如何避免“instanceof”?
【发布时间】:2015-06-10 02:34:15
【问题描述】:

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

for (Blueprint bp : blueprints) {
    Vehicle v = VehicleFactory.buildVehicle(bp);
    allVehicles.add(v);
                
    // Can I accomplish this without using 'instanceof'?
    if (v instanceof Car) {
        cars.add((Car) v);
    } else if (v instanceof Boat) {
        boats.add((Boat) v);
    } else if (v instanceof Plane) {
        planes.add((Plane) v);
    }
}

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

我欢迎对我的实施提出任何反馈/建议,因为我什至不确定我是否以正确的方式进行。

下面的完整示例:

import java.util.ArrayList;

class VehicleManager {
    
    public static void main(String[] args) {
        
        ArrayList<Blueprint> blueprints = new ArrayList<Blueprint>();
        ArrayList<Vehicle> allVehicles = new ArrayList<Vehicle>();
        ArrayList<Car> cars = new ArrayList<Car>();
        ArrayList<Boat> boats = new ArrayList<Boat>();
        ArrayList<Plane> planes = new ArrayList<Plane>();
        
        /*
        *  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.
        */
        Blueprint bp0 = new Blueprint(0);
        Blueprint bp1 = new Blueprint(1);
        Blueprint bp2 = new Blueprint(2);
        blueprints.add(bp0);
        blueprints.add(bp1);
        blueprints.add(bp2);
        
        for (Blueprint bp : blueprints) {
            Vehicle v = VehicleFactory.buildVehicle(bp);
            allVehicles.add(v);
            
            // Can I accomplish this without using 'instanceof'?
            if (v instanceof Car) {
                cars.add((Car) v);
            } else if (v instanceof Boat) {
                boats.add((Boat) v);
            } else if (v instanceof Plane) {
                planes.add((Plane) v);
            }
        }
        
        System.out.println("All Vehicles:");
        for (Vehicle v : allVehicles) {
            System.out.println("Vehicle: " + v + ", maxSpeed: " + v.maxSpeed);
        }
        
        System.out.println("Cars:");
        for (Car c : cars) {
            System.out.println("Car: " + c + ", numCylinders: " + c.numCylinders);
        }
        
        System.out.println("Boats:");
        for (Boat b : boats) {
            System.out.println("Boat: " + b + ", numRudders: " + b.numRudders);
        }
        
        System.out.println("Planes:");
        for (Plane p : planes) {
            System.out.println("Plane: " + p + ", numPropellers: " + p.numPropellers);
        }
    }
}

class Vehicle {
    
    double maxSpeed;
    
    Vehicle(double maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
}

class Car extends Vehicle {
    
    int numCylinders;
    
    Car(double maxSpeed, int numCylinders) {
        super(maxSpeed);
        this.numCylinders = numCylinders;
    }
}

class Boat extends Vehicle {
    
    int numRudders;
    
    Boat(double maxSpeed, int numRudders) {
        super(maxSpeed);
        this.numRudders = numRudders;
    }
}

class Plane extends Vehicle {
    
    int numPropellers;
    
    Plane(double maxSpeed, int numPropellers) {
        super(maxSpeed);
        this.numPropellers = numPropellers;
    }
}

class VehicleFactory {
    
    public static Vehicle buildVehicle(Blueprint blueprint) {
        
        switch (blueprint.type) {
            
            case 0:
                return new Car(100.0, 4);
                
            case 1:
                return new Boat(65.0, 1);
                
            case 2:
                return new Plane(600.0, 2);
                
            default:
                return new Vehicle(0.0);
        }
    }
}

class Blueprint {
    
    int type; // 0 = car; // 1 = boat; // 2 = plane;
    
    Blueprint(int type) {
        this.type = type;
    }
}

【问题讨论】:

  • 您可以首先为您的字段添加 getter 和 setter,例如 maxSpeednumPropellers。这称为信息隐藏。您可以在此处阅读更多信息:en.wikipedia.org/wiki/Information_hiding。接下来,您可以定义一个名为 VehicleType 的 Enum,而不是使用诸如 0 或 1 之类的数字来表示车辆类型。这将使代码更具可读性。
  • 不能每个AVehicle 子类都覆盖toString()?然后您可以将它们全部打印出来而不必担心类型。如果调用者需要知道类型的其他原因,请告诉我们,我们可以提出其他建议。
  • 工厂模式被设计为对使用它的程序员隐藏AVehicle 的子类(关键字封装)。你确定工厂模式是适合你的设计模式吗?

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


【解决方案1】:

你可以实现Visitor pattern


详细解答

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

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

我们可以将对象传递给Vehicle 中声明的方法。如果vehicleCar 类型,并且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();
    }
}

【讨论】:

  • 虽然这行得通,但我认为这个“解决方案”比他试图解决的问题要糟糕得多。
  • @LorenPechtel 如果您能详细说明情况如何变得更糟,那就太好了。
  • @LorenPechtel,visitor 是最优雅的解决方案,尽管对于未经训练的人来说它看起来很奇怪。它允许将单个调度转换为多个调度。基本理念是,“不要打电话给我们,我们会打电话给你。”
  • 这正是我正在寻找的解决方案。我知道它可以做得更好,但我只是不确定如何做。非常感谢您花时间详细解释这一点!非常感谢。
  • @LorenPechtel A Vehicle 将自己添加到列表中。访客进行添加。 Vehicle 实例不知道 VehicleManager
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    相反,使用类型为 car、boat 和 plane 的 enum 怎么样。您有一系列车辆列表。

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

    【讨论】:

    • 我必须同意,使用多个列表是一种失败。但是您建议的enum 系统不允许每种车辆子类型(CarBoat)拥有自己独特的属性(船有方向舵,汽车有圆柱体等),也不允许多个实例。他可以为每种类型(enum CarType)创建多个enum 声明,然后编写Vehicle,但这是另一个设计缺陷:对象不应该由类型组成。对象应该是 of 一种类型(是该类型还是它的子类型)。另外,为特定枚举编写实现可能会使文件变得非常庞大
    • @VinceEmigh 当然可以。我并不是说要删除他已经拥有的代码,只是添加一个标识如何对车辆进行分类的属性。将其用作包含它们的列表数组的索引。我的版本使用的语句比他不喜欢的实例版本少。
    • 这并不总是关于使用了多少语句。许多其他事情在编写“高效代码”方面发挥着作用。它只是关于要回避多少行代码或语句,带有 getter 方法的私有字段。
    • @VinceEmigh Private??将其放入列表的例程必须看到它,它不能是私有的。
    • 我并没有说我的回答是私人的。那句话是一个例子,说明更少的代码并不总是更好。公共字段消除了对 getter 方法的需求,但允许可变性。代码行数并不像你想象的那么重要。一切都与设计有关..
    【解决方案4】:

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

        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?) 中展示的访问者设计模式来克服它。

    【讨论】:

    • 问题是:如何避免类型检查,这是一种设计味道。使用 Class.isInstance 而不是 instanceof 不会删除类型检查。
    • AVehicle 的任何实例都不应该承担add() 本身的责任……等等,即使add() 方法实现错误。在这段代码中调用add() 只是每次都将自己添加到自己的列表中。这是一种倒退。
    • @Anshuman 抱歉,现在我在每个类的字段上看到了 static 修饰符......不过,您的解决方案强制理解任何 AVehicle 实例都知道如何到 add() 本身到something,这使得这是一个非常神秘的方法。如果我们不是添加到List,而是添加到SetMapSecretDungeonOfMotorizedAssets 等呢?还是会有很多地方更新……
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

        如果您无法控制 AVehicle 类怎么办?例如。你有它从一些 3rd 方库?所以你没有办法添加访问者模式的 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 方法的问题是您必须注册所有可能的类,包括所有已知的子类。尽管如果您有巨大的层次结构并且您的任务不需要所有类,您可以节省大量在 Map 中注册所需的工作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-27
          • 1970-01-01
          相关资源
          最近更新 更多