¿Cómo evitar ‘instanceof’ al implementar el patrón de diseño de fábrica?

Estoy intentando implementar mi primer patrón de diseño de fábrica, y no estoy seguro de cómo evitar el uso de instancias cuando se agregan los objetos fabricados a las listas. Esto es lo que trato de hacer:

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

Por lo que he leído en SO, usar ‘instanceof’ es un olor a código. ¿Hay una mejor manera de verificar el tipo de vehículo que fue creado por la fábrica sin usar ‘instanceof’?

Agradezco cualquier comentario / sugerencia sobre mi implementación, ya que no estoy seguro de si voy por el camino correcto.

Ejemplo completo a continuación:

 import java.util.ArrayList; class VehicleManager { public static void main(String[] args) { ArrayList bluePrints = new ArrayList(); ArrayList allVehicles = new ArrayList(); ArrayList cars = new ArrayList(); ArrayList boats = new ArrayList(); ArrayList planes = new ArrayList(); /* * 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; } } 

Podría implementar el patrón Visitor .


Respuesta detallada

La idea es usar polymorphism para realizar la verificación de tipo. Cada subclase anula el método de accept(Visitor) , que debe declararse en la superclase. Cuando tenemos una situación como esta:

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

Podemos pasar un objeto a un método declarado en el Vehicle . Si el vehicle es de tipo Car y la class Car anula el método en el que pasamos el objeto, ese objeto ahora se procesará dentro del método declarado en la clase Car . Usamos esto para nuestra ventaja: crear un objeto Visitor y pasarlo a un método sobrescrito:

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

Este Visitor debe estar preparado para visitar el tipo de Car . Debe especificarse en el Visitor cualquier tipo que desee evitar al usar instanceof para encontrar el tipo real de.

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

¡Aquí es donde sucede la verificación de tipos!

Cuando el Car recibe al visitante, debe pasar el uso de this palabra clave. Como estamos en la clase Car , se invoca la visit(Car) al método visit(Car) . Dentro de nuestro visitante, podemos realizar la acción que queremos, ahora que sabemos el tipo de objeto.


Entonces, desde arriba:

Usted crea un Visitor , que realiza las acciones que desea. Un visitante debe consistir en un método de visit para cada tipo de objeto sobre el que desea realizar una acción. En este caso, estamos creando un visitante para vehículos:

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

La acción que queremos realizar es agregar el vehículo a algo. Creamos un AddTransportVisitor ; un visitante que maneja la adición de transportes:

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

Cada vehículo debería poder aceptar visitantes del vehículo:

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

Cuando un visitante se pasa a un vehículo, el vehículo debe invocar su método de visit , pasándose a los argumentos:

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

Ahí es donde sucede la verificación de tipos. Se llama al método de visit correcto, que contiene el código correcto para ejecutar en función de los parámetros del método.

El último problema es hacer que VehicleVisitor interactúe con las listas. Aquí es donde entra su VehicleManager : encapsula las listas, lo que le permite agregar vehículos a través de un método VehicleManager#add(Vehicle) .

Cuando creamos el visitante, podemos pasarle el administrador (posiblemente a través de su constructor), para que podamos realizar la acción que queremos, ahora que sabemos el tipo de objeto. El VehicleManager debe contener al visitante e interceptar las llamadas de VehicleManager#add(Vehicle) :

 class VehicleManager { private List carList = new ArrayList<>(); private List boatList = new ArrayList<>(); private List planeList = new ArrayList<>(); private AddTransportVisitor addVisitor = new AddTransportVisitor(this); public void add(Vehicle vehicle) { vehicle.accept(addVisitor); } public List getCarList() { return carList; } public List getBoatList() { return boatList; } public List getPlaneList() { return planeList; } } 

Ahora podemos escribir implementaciones para los métodos de 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); } } 

Recomiendo eliminar los métodos get y declarar métodos de carga sobrecargados para cada tipo de vehículo. Esto reducirá los gastos generales de “visitar” cuando no sea necesario, por ejemplo, manager.add(new Car()) :

 class VehicleManager { private List carList = new ArrayList<>(); private List boatList = new ArrayList<>(); private List 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(); } } 

Puede agregar el método a la clase del vehículo para imprimir el texto. Luego anule el método en cada clase de automóvil especializado. Luego solo agrega todos los autos a la lista de vehículos. Y recorra la lista para imprimir el texto.

No estoy muy contento con las listas de autos, barcos y aviones en primer lugar. Usted tiene múltiples ejemplos de realidad, pero la lista no es intrínsecamente exhaustiva: ¿qué sucede cuando su fábrica comienza a fabricar submarinos o cohetes?

En cambio, ¿qué tal una enumeración con los tipos de coche, barco y avión. Usted tiene una variedad de listas de vehículos.

El vehículo genérico tiene una propiedad abstracta Catalogas, los diversos vehículos realmente implementan esto y devuelven el valor adecuado.

He realizado una reestructuración de tu código. Espero que eso te funcione. Mira esto:

  import java.util.ArrayList; class VehicleManager { public static void main(String[] args) { ArrayList bluePrints = new ArrayList(); ArrayList allVehicles = new ArrayList(); ArrayList cars = null; ArrayList boats = null; ArrayList 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 cars = new ArrayList(); int numCylinders; ACar(double maxSpeed, int numCylinders) { super(maxSpeed); this.numCylinders = numCylinders; } void add(){ cars.add(this); } public static ArrayList getCars(){ return cars; } } class ABoat extends AVehicle { static ArrayList boats = new ArrayList(); int numRudders; ABoat(double maxSpeed, int numRudders) { super(maxSpeed); this.numRudders = numRudders; } void add(){ boats.add(this); } public static ArrayList getBoats(){ return boats; } } class APlane extends AVehicle { static ArrayList planes = new ArrayList(); int numPropellers; APlane(double maxSpeed, int numPropellers) { super(maxSpeed); this.numPropellers = numPropellers; } void add(){ planes.add(this); } public static ArrayList 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; } } 

Con el código anterior, la clase tendrá que saber sobre la colección a la que se debe agregar. Esto puede considerarse como un inconveniente para un buen diseño y se puede superar utilizando el patrón de diseño del visitante como se demuestra en la respuesta aceptada ( ¿Cómo evitar ‘instancia’ al implementar el patrón de diseño de fábrica? ).

Tenía un problema similar, así que usé este patrón, para entenderlo mejor, creé un dibujo UML simple que mostraba la secuencia de cosas en los comentarios (sigue los números). Utilicé la solución de Vince Emighs anterior. La solución de patrón es más elegante, pero puede requerir cierto tiempo para comprender realmente. Requiere una interfaz y una clase más que el original, pero son muy simples.

el original está en el lado derecho, la solución que usa el patrón de visitante está en el lado izquierdo

Sé que ha pasado mucho tiempo desde que se hizo esta pregunta. Encontré http://www.nurkiewicz.com/2013/09/instanceof-operator-and-visitor-pattern.html que parece ser útil. Compartirlo aquí en caso de que alguien esté interesado.