在实现工厂devise模式时如何避免“instanceof”?

我试图实现我的第一个工厂devise模式,我不知道如何避免使用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); } } 

从我读过的东西,使用'instanceof'是一种代码味道。 有没有更好的方法来检查工厂创build的车型,而不使用“instanceof”?

我欢迎任何关于我的实施的反馈/build议,因为我甚至不确定我是否正确地采取这种方式。

下面的完整例子:

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

您可以实现访问者模式 。


详细的答案

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

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

我们可以将一个对象传递给Vehicle声明的方法。 如果vehicleCartypes的,并且class Car取代了我们将对象传入的方法,则该对象现在将在Car类中声明的方法内处理。 我们使用这个优势:创build一个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应该准备参观typesCar 。 您希望避免使用instanceof查找实际types的任何types都必须在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... } } 

这里是types检查的地方!

Car收到访问者时,应该使用this关键字。 由于我们在Car类,所以方法visit(Car)将被调用。 在我们访客的内部,我们可以执行我们想要的操作,现在我们知道对象的types。


所以,从顶部:

您创build一个Visitor ,执行您想要的操作。 访问者应该包含一个visit方法,用于对要执行操作的每种types的对象进行访问。 在这种情况下,我们正在创build一个车辆访问者:

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

我们要执行的行动是增加车辆的东西。 我们将创build一个AddTransportVisitor ; pipe理添加运输的访客:

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

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

最后一个问题是VehicleVisitor与列表交互。 这是您的VehicleManager来源:它封装列表,允许您通过VehicleManager#add(Vehicle)方法VehicleManager#add(Vehicle)

当我们创build访问者时,我们可以通过pipe理器(可能通过它的构造函数),所以我们可以执行我们想要的操作,现在我们知道对象的types。 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); } } 

我强烈build议删除getter方法并为每种types的车辆声明重载的add方法。 这将减less不必要的“访问”的开销,例如, 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(); } } 

您可以将方法添加到车辆类来打印文本。 然后重写每个专门的Car类中的方法。 然后,将所有车辆添加到车辆列表。 并循环列表打印文本。

我不太喜欢汽车,小船和飞机的名单。 你有很多现实的例子,但是这个列表本质上并不是全面的 – 当你的工厂开始制造潜艇或者火箭时会发生什么?

相反,如何与types的汽车,船和飞机枚举。 你有一系列的车辆列表。

通用汽车有一个抽象的属性CatalogAs,各种车辆实际上实现这一点,并返回适当的价值。

对代码进行一些重构。 希望这对你有用。 检查这个:

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

通过上面的代码,这个类将不得不知道要添加的集合。 这可以被认为是一个好的devise的缺点,它可以使用访问者的devise模式来克服,如接受的答案( 如何在实现工厂devise模式时避免'instanceof' )所示。

有一个类似的问题,所以我使用这种模式,为了更好地理解它,我创build了一个简单的UMLgraphics,显示注释中的顺序(按照数字)。 我上面使用了Vince Emighs解决scheme。模式解决scheme更加优雅,但需要一些时间才能真正理解。 它需要一个接口和一个类,而不是原来的,但它们非常简单。

原来是在右侧,使用访客模式的解决方案是在左侧