Nimrod
Nimrod

Reputation: 1140

Alternative implementation of INSTANCEOF when there are 'many' subclasses'

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

Answers (4)

charlie
charlie

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

bananas
bananas

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

Falla Coulibaly
Falla Coulibaly

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

Sam
Sam

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

Related Questions