Как избежать "instanceof" при реализации шаблона проектирования фабрики?
Я пытаюсь реализовать свой первый шаблон Factory Design Pattern, и я не уверен, как избежать использования 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);
}
}
Из того, что я читал на SO, использование instanceof - это запах кода. Есть ли лучший способ проверить тип транспортного средства, созданного на заводе, без использования instanceof?
Я приветствую любые отзывы / предложения по поводу моей реализации, так как я даже не уверен, правильно ли я поступил.
Полный пример ниже:
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;
}
}
7 ответов
Вы можете реализовать шаблон посетителя.
Подробный ответ
Идея состоит в том, чтобы использовать полиморфизм для проверки типов. Каждый подкласс переопределяет accept(Visitor)
метод, который должен быть объявлен в суперклассе. Когда у нас такая ситуация:
void add(Vehicle vehicle) {
//what type is vehicle??
}
Мы можем передать объект в метод, объявленный в Vehicle
, Если vehicle
имеет тип 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
}
}
это 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);
}
}
Я настоятельно рекомендую удалить методы получения и объявить перегруженными 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();
}
}
Вы можете добавить метод к классу транспортного средства, чтобы напечатать текст. Затем переопределите метод в каждом специализированном классе Car. Затем просто добавьте все автомобили в список транспортных средств. И зациклите список, чтобы напечатать текст.
Сделана некоторая реструктуризация вашего кода. Надеюсь, что это работает для вас. Проверь это:
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;
}
}
С помощью приведенного выше кода класс должен знать о коллекции, в которую он должен быть добавлен. Это может рассматриваться как недостаток хорошего дизайна, и его можно преодолеть с помощью шаблона дизайна посетителя, как показано в принятом ответе ( Как избежать "instanceof" при реализации шаблона проектирования фабрики?).
Я знаю, что прошло много времени с тех пор, как был задан этот вопрос. Я нашел http://www.nurkiewicz.com/2013/09/instanceof-operator-and-visitor-pattern.html который выглядит полезным. Поделиться здесь на случай, если кому-то будет интересно.
Я не слишком доволен списками машин, лодок и самолетов в первую очередь. У вас есть несколько примеров реальности, но этот список не является всеобъемлющим - что произойдет, когда ваша фабрика начнет производить подводные лодки или ракеты?
Вместо этого, как насчет перечисления типов автомобилей, лодок и самолетов. У вас есть массив списков транспортных средств.
Универсальное транспортное средство имеет абстрактное свойство CatalogAs, которое фактически реализуют различные транспортные средства и возвращают правильное значение.
У меня была похожая проблема, поэтому я использовал этот шаблон, чтобы лучше понять его, я создал простой чертеж UML, показывающий последовательность вещей в комментариях (следуйте номерам). Я использовал решение Винса Эмигса выше. Шаблонное решение более элегантно, но может потребовать некоторого времени, чтобы по-настоящему понять. Это требует одного интерфейса и одного класса больше, чем оригинал, но они очень просты.
Что если классы AVehicle находятся вне вашего контроля? Например, у вас есть это из какой-то сторонней библиотеки? Таким образом, у вас нет возможности добавить метод accept() шаблона Visitor. Также вам, возможно, не понравится шаблонный код в каждом из подклассов 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 состоит в том, что вы должны зарегистрировать все возможные классы, включая все известные подклассы. Хотя, если у вас огромная иерархия и вам не нужны все классы для вашей задачи, вы можете сохранить много работы, зарегистрировав на карте только нужные.