Reputation: 1140
I've tried looking for a proper solution, but had difficulties finding it.
This is quite a simple question - is there a proper, effective alternative of using INSTACEOF, when we want to determine a class type when there are a lot of that class's instancess?
For example:
We have a parent Product
class
public abstract class Product {
public abstract String preview();
}
Which has 3 subclasses (Vinyl, Book and Video)
public class Vinyl extends Product {
@Override
public String preview() {
return "Playing a vinyl";
}
}
public class Book extends Product {
@Override
public String preview() {
return "Reading a book";
}
}
@Override
public String preview() {
return "Playing a video";
}
Now, I want to check according for each product type it's preview method returned value - When we have 3 subclasses, it's quite clear and neat when writing:
public class TestProduct {
List<Product> productList = new ArrayList<>();
public void preview(Product product) {
for (Product currentProduct : productList) {
String preview = currentProduct.preview();
if (product instanceof Book) {
Assert.assertEquals(preview, "Reading a book");
} else if (product instanceof Vinyl) {
Assert.assertEquals(preview, "Playing vinyl");
} else {
Assert.assertEquals(preview, "Playing video");
}
}
}
}
But what if we don't have 3 product, but 100 or more?
It's doesn't make me any sense writing a 100 times this INSTACEOF
statements..
Is there any proper, scalable way?
Upvotes: 2
Views: 303
Reputation: 1527
It's not always practical or possible to use polymorphism or the Visitor pattern, e.g. when you can't change a subclass, or when there are many different situations where you want to doSomething()
based on a specific subtype, e.g. when you have many different decorators — there you don't want that logic in subclasses themselves. In such situations you can use an external handler, like:
class Handler<T> {
private Map<Class<? extends T>, Consumer<? super T>> handlers = new HashMap<>();
Handler<T> register(Class<? extends T> subtype, Consumer<? super T> action) {
handlers.put(subtype, action);
return this;
}
void handle(T instance) {
Optional.ofNullable(instance)
.map(Object::getClass)
.map(this.handlers::get)
.orElseThrow(() -> new IllegalStateException("Unknown subtype!"))
.accept(instance);
}
}
Usage:
Handler<Product> productHandler = new Handler<Product>()
.register(Vinil.class, vinil -> gramophone.play(vinil.sideB()))
.register(Book.class, book -> library.put(book.isbn(), book))
.register(Video.class, video -> { /* no-op */ });
products.stream()
.forEach(productHandler::handle);
Upvotes: 1
Reputation: 1196
one possible solution is to enjoy the polymorphism
public static void check(Object o) {
List<? extends Products> list = Arrays.asList(new A(), new B(), new C());
if (o instanceof Products) {
Products p = (Products) o;
p.doSomething();
}
}
and the interface is
public interface Products {
public void doSomething();
}
Upvotes: 1
Reputation: 769
This is a possible implementation using interface
:
public interface class Product {
public void doSomething();
}
Then each subclass will have to implement its own doSomething()
public class Vinyl implements Product {
public void doSomething(){
System.out.println("Vinyl product");
}
}
public class Book implements Product {
public void doSomething(){
System.out.println("Book product");
}
}
public class Video implements Product {
public void doSomething(){
System.out.println("Video product");
}
}
Then
Product product = new Vinyl();
product.doSomething();
// prints on the console --> "Vinyl product"
Same goes for the other classes that implements the interface product.
Abstract classes cannot be instantiated which in your case is of no help that's why I used interface.
Side note: generally when you need to use instanceof
, then you are probably using the wrong design.
Upvotes: 3
Reputation: 9944
Why not make the work you're doing part of the subclass itself?
public abstract class Product {
abstract void doSomething();
}
Then the code doing the looping doesn't need to know what type of product it has. It can let the product decide what to do.
for (Product product : products) {
product.doSomething();
}
The doSomething()
method can have arguments and a return type if you need.
You can also use the Visitor pattern for a variation on the same theme that lets you specify the implementation outside of the actual Product
implementation class.
Upvotes: 2