tutorial patrones patron implementar example ejemplos diseño con code best java design-patterns factory factory-pattern instanceof

implementar - patrones de diseño java tutorial



¿Cómo evitar ''instanceof'' al implementar el patrón de diseño de fábrica? (7)

¿Qué pasa si las clases de AVehicle están fuera de tu control? Por ejemplo, ¿lo tienes de alguna biblioteca de terceros? Por lo tanto, no tiene forma de agregar el método accept () del patrón Visitor. También es probable que no le guste el código repetitivo en cada una de las subclases de AVehicle y prefiera poner todo en una clase especial para mantener sus clases limpias. En algunos casos, podría ser mejor usar HashMap.

En su muestra solo 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); }

El problema con este enfoque de HashMap es que debe registrar todas las clases posibles, incluidas todas las subclases conocidas. Aunque si tiene una gran jerarquía y no se necesitan todas las clases para su tarea, puede ahorrar mucho trabajo registrando en el Mapa solo las necesarias.

Estoy intentando implementar mi primer patrón de diseño de fábrica, y no estoy seguro de cómo evitar usar instanceof al agregar los objetos fabricados de fábrica a las listas. Esto es lo que estoy tratando 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 ni siquiera estoy seguro de si voy a hacerlo de la manera correcta.

Ejemplo completo a continuación:

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


En primer lugar, no estoy muy contento con las listas de automóviles, barcos y aviones. Tiene múltiples ejemplos de la realidad, pero la lista no es inherentemente exhaustiva: ¿qué sucede cuando su fábrica comienza a fabricar submarinos o cohetes?

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

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


Hecho una reestructuración de su código. Espero que eso te funcione. Mira esto:

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

Con el código anterior, la clase tendrá que saber sobre la colección a la que debe agregarse. Esto puede considerarse como una desventaja de un buen diseño y puede superarse utilizando el patrón de diseño del visitante como se demuestra en la respuesta aceptada ( ¿Cómo evitar ''instancia de'' cuando se implementa el patrón de diseño de fábrica? ).


Puede agregar un método a la clase de vehículo para imprimir el texto. Luego anule el método en cada clase especializada de automóviles. Luego solo agregue todos los autos a la lista de vehículos. Y repita la lista para imprimir el texto.


Puede implementar el patrón de visitante .

Respuesta detallada

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

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

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

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 . Cualquier tipo que desee evitar utilizando instanceof para encontrar el tipo real debe especificarse en el 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... } }

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

Cuando el Car recibe al visitante, debe pasarse usando la palabra clave this . Como estamos en la clase Car , se invocará la visit(Car) 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:

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

Todos los vehículos deben poder aceptar visitantes de vehículos:

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

Cuando un visitante 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 tipo. 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 el VehicleVisitor interactúe con las listas. Aquí es donde entra su VehicleManager : encapsula las listas, permitiéndole 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 queramos, ahora que sabemos el tipo de objeto. El VehicleManager debe contener el visitante e interceptar las llamadas de 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; } }

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

Le recomiendo eliminar los métodos getter y declarar métodos add sobrecargados para cada tipo de vehículo. Esto reducirá la sobrecarga de "visitas" cuando no sea necesario, por ejemplo, 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(); } }



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