How to avoid 'instanceof' when implementing factory design pattern?
You could implement the Visitor pattern.
Detailed Answer
The idea is to use polymorphism to perform the type-checking. Each subclass overrides the accept(Visitor)
method, which should be declared in the superclass. When we have a situation like:
void add(Vehicle vehicle) {
//what type is vehicle??
}
We can pass an object into a method declared in Vehicle
. If vehicle
is of type Car
, and class Car
overrode the method we passed the object into, that object would now be processed within the method declared in the Car
class. We use this to our advantage: creating a Visitor
object and pass it to an overriden method:
abstract class Vehicle {
public abstract void accept(AddToListVisitor visitor);
}
class Car extends Vehicle {
public void accept(AddToListVisitor visitor) {
//gets handled in this class
}
}
This Visitor
should be prepared to visit type Car
. Any type that you want to avoid using instanceof
to find the actual type of must be specified in the 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...
}
}
Here's where the type checking happens!
When the Car
receives the visitor, it should pass itself in using the this
keyword. Since we are in class Car
, the method visit(Car)
will be invoked. Inside of our visitor, we can perform the action we want, now that we know the type of the object.
So, from the top:
You create a Visitor
, which performs the actions you want. A visitor should consist of a visit
method for each type of object you want to perform an action on. In this case, we are creating a visitor for vehicles:
interface VehicleVisitor {
void visit(Car car);
void visit(Plane plane);
void visit(Boat boat);
}
The action we want to perform is adding the vehicle to something. We would create an AddTransportVisitor
; a visitor that manages adding transportations:
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
}
}
Every vehicle should be able to accept vehicle visitors:
abstract class Vehicle {
public abstract void accept(VehicleVisitor visitor);
}
When a visitor is passed to a vehicle, the vehicle should invoke it's visit
method, passing itself into the arguments:
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);
}
}
That's where the type-checking happens. The correct visit
method is called, which contains the correct code to execute based on the method's parameters.
The last problem is having the VehicleVisitor
interact with the lists. This is where your VehicleManager
comes in: it encapsulates the lists, allowing you to add vehicles through a VehicleManager#add(Vehicle)
method.
When we create the visitor, we can pass the manager to it (possibly through it's constructor), so we can perform the action we want, now that we know the object's type. The VehicleManager
should contain the visitor and intercept VehicleManager#add(Vehicle)
calls:
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;
}
}
We can now write implementations for the AddTransportVisitor#visit
methods:
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);
}
}
I highly suggest removing the getter methods and declaring overloaded add
methods for each type of vehicle. This will reduce overhead from "visiting" when it's not needed, for example, 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();
}
}
Avoiding instanceof in Java
You might be interested in this entry from Steve Yegge's Amazon blog: "when polymorphism fails". Essentially he's addressing cases like this, when polymorphism causes more trouble than it solves.
The issue is that to use polymorphism you have to make the logic of "handle" part of each 'switching' class - i.e. Integer etc. in this case. Clearly this is not practical. Sometimes it isn't even logically the right place to put the code. He recommends the 'instanceof' approach as being the lesser of several evils.
As with all cases where you are forced to write smelly code, keep it buttoned up in one method (or at most one class) so that the smell doesn't leak out.
Avoiding 'instanceof' in Java
The simplest approach is to have the Event provide a method you can call so the Event knows what to do.
interface Event {
public void onEvent(Context context);
}
class DocumentEvent implements Event {
public void onEvent(Context context) {
context.getDocumentGenerator().gerenateDocument(this);
}
}
class MailEvent implements Event {
public void onEvent(Context context) {
context.getDeliveryManager().deliverMail(event);
}
}
class Context {
public void divideEvent(Event event) {
event.onEvent(this);
}
}
Avoiding instanceof Java
Indeed, this could be solved with a visitor pattern.
However, it does not have to be a full-blown visitor, but a simplified variation of it. You could pass the inventory to the item and let the item do whatever it wants with it:
abstract class Item {
public abstract void equip(Inventory inv);
}
class HelmItem extends Item {
@Override
public void equip(Inventory inv) {
inv.setHelm(this);
}
}
class WeaponItem extends Item {
@Override
public void equip(Inventory inv) {
inv.setWeapon(this);
}
}
Then you can just call:
it.equip(inv)
without the instanceof
operator.
How to avoid large if-statements and instanceof
An elegant way of avoiding instanceof
without inventing some new artificial method in the base class (with a non-descriptive name such as performAction
or doWhatYouAreSupposedToDo
) is to use the visitor pattern. Here is an example:
Animal
import java.util.*;
abstract class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public abstract void accept(AnimalVisitor av); // <-- Open up for visitors.
}
Lion and Deer
class Lion extends Animal {
public Lion(String name) {
super(name);
}
public void roar() {
System.out.println("Roar");
}
public void accept(AnimalVisitor av) {
av.visit(this); // <-- Accept and call visit.
}
}
class Deer extends Animal {
public Deer(String name) {
super(name);
}
public void runAway() {
System.out.println("Running...");
}
public void accept(AnimalVisitor av) {
av.visit(this); // <-- Accept and call visit.
}
}
Visitor
interface AnimalVisitor {
void visit(Lion l);
void visit(Deer d);
}
class ActionVisitor implements AnimalVisitor {
public void visit(Deer d) {
d.runAway();
}
public void visit(Lion l) {
l.roar();
}
}
TestAnimals
public class TestAnimals {
public static void main(String[] args) {
Animal lion = new Lion("Geo");
Animal deer1 = new Deer("D1");
Animal deer2 = new Deer("D2");
List<Animal> li = new ArrayList<Animal>();
li.add(lion);
li.add(deer1);
li.add(deer2);
for (Animal a : li)
a.accept(new ActionVisitor()); // <-- Accept / visit.
}
}
Related Topics
Printing All Variables Value from a Class
How to Replace Case-Insensitive Literal Substrings in Java
Before and After Suite Execution Hook in Junit 4.X
Convert Boolean to Int in Java
Are Java Static Initializers Thread Safe
How to Calculate the Intersection of Two Sets
What Is the Purpose of Flush() in Java Streams
String to String Array Conversion in Java
Finding Key Associated with Max Value in a Java Map
Java - Get a List of All Classes Loaded in the Jvm
&& (And) and || (Or) in If Statements