Как избежать "экземпляра" при реализации шаблона проектирования factory?
Я пытаюсь реализовать свой первый шаблон дизайна Factory, и я не уверен, как избежать использования экземпляра при добавлении объектов factory в списки. Это то, что я пытаюсь сделать:
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" - это запах кода. Есть ли лучший способ проверить тип транспортного средства, созданного с помощью Factory, не используя '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;
}
}
Ответы
Ответ 1
Вы можете реализовать шаблон посетителя.
Подробный ответ
Идея состоит в использовании polymorphism для выполнения проверки типов. Каждый подкласс переопределяет метод accept(Visitor)
, который должен быть объявлен в суперклассе. Когда у нас есть такая ситуация, как:
void add(Vehicle vehicle) {
//what type is vehicle??
}
Мы можем передать объект в метод, объявленный в Vehicle
. Если Vehicle
имеет тип Car
, а class Car
переопределяет метод, в который мы передали объект, этот объект теперь обрабатывается в методе, объявленном в классе Car
. Мы используем это в наших интересах: создаем объект Visitor
и передаем его методу override:
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();
}
}
Ответ 2
Вы можете добавить метод к классу автомобиля для печати текста. Затем переопределите метод в каждом специализированном классе Car. Затем просто добавьте все автомобили в список транспортных средств. И зациклируйте список, чтобы распечатать текст.
Ответ 3
Сделали некоторую реструктуризацию вашего кода. Надеюсь, это сработает для вас. Проверьте это:
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;
}
}
С приведенным выше кодом класс должен знать о коллекции, к которой он должен быть добавлен. Это можно рассматривать как недостаток хорошего дизайна, и его можно преодолеть с использованием шаблона дизайна посетителя, как показано в принятом ответе (Как избежать "экземпляра" при реализации дизайна factory шаблон?).
Ответ 4
Я не слишком доволен списками автомобилей, лодок и самолетов в первую очередь. У вас есть несколько примеров реальности, но список не является по своей сути всеобъемлющим - что происходит, когда ваш factory начинает создавать подводные лодки или ракеты?
Вместо этого, как насчет перечисления с типами автомобилей, лодок и самолетов. У вас есть список списков транспортных средств.
Общий автомобиль имеет абстрактное свойство CatalogAs, различные транспортные средства фактически реализуют это и возвращают правильное значение.
Ответ 5
Имел подобную проблему, поэтому я использовал этот шаблон, чтобы лучше понять его. Я создал простой рисунок UML, показывающий последовательность вещей в комментариях (следуйте номерам). Я использовал решение Vince Emighs выше. Решение шаблона более изящно, но может потребоваться некоторое время, чтобы по-настоящему понять. Для этого требуется один интерфейс и один класс больше оригинала, но они очень просты.
![оригинал находится с правой стороны, решение с использованием шаблона посетителя находится слева]()