Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Processing an object depending on its implementation of an interface in Java

Tags:

java

I have been looking for design ideas to solve this problem in Java.

I am using a library (I cannot change it) that for this example, I'll just call "Animals". It contains an Animal interface, and a bunch of implementations; and I need to call different methods according to the implementation of the Animal I get:

List<Animal> animals = Service.getAnimals();

for(Animal a : animals) {
    process(a);
}

private void process(Animal animal) {

    if (animal instanceOf Cat) {
        processCat(animal);
    } else if (animal instanceOf Dog) {
        processDog(animal);
    } else {
        System.out.println("unsopported animal");
    }
}

I am currently solving this through reflection, with a class that holds all "Processors" and calling them using

String methodName = "process" + Animal getClass().getSimpleName(); //ugh

I am using Java 8 and I am pretty sure there must be better design to address this issue.

Any help is appreciated!

like image 666
Eduardo Coria Avatar asked Jun 05 '18 00:06

Eduardo Coria


People also ask

Can we create object implementation to an interface in Java?

No, you cannot instantiate an interface.

Can an object implement an interface?

If you define a reference variable whose type is an interface, any object you assign to it must be an instance of a class that implements the interface. By casting object1 to a Relatable type, it can invoke the isLargerThan method.

How is interface implemented in Java?

The implements keyword is used to implement an interface . The interface keyword is used to declare a special type of class that only contains abstract methods. To access the interface methods, the interface must be "implemented" (kinda like inherited) by another class with the implements keyword (instead of extends ).

Can multiple classes implement the same interface in Java?

Your class can implement more than one interface, so the implements keyword is followed by a comma-separated list of the interfaces implemented by the class. By convention, the implements clause follows the extends clause, if there is one.


2 Answers

If Animal's is a sealed class, that is, it isn't dynamically extendable, and has a limited, known number of sub-classes, Then the if-instanceof pattern you have stumbled across in the example is classic "pattern matching".

If Animal was a class you could control, then you could use the Visitor Pattern to create a visit method directly on Animal.

However you state that Animal is from an external library, which limits the approach you can take.

You can still use the Visitor pattern, keeping all code responsible for interacting with Animals in a single class, using method overloading to resolve the type at runtime (assuming you don't have any issues with generics).

But really this is just as inflexible as if-instanceof method, it just makes OO people feel better.

So, the approach to take comes down to code organization, and what makes sense for your code base.

Honestly, the if-instanceof is what I would go for, unless the amount of methods / behaviors starts getting too complex.

In which case, I'd create a type of registry, that registers a processor for each of the animal types.

Then, you can create a simple class that gets the required processor from the registry for the type of Animal.

This registry pattern I've seen used a lot in Minecraft, but I'm not sure if it's documented elsewhere.

Essentially it's use would look something like this.

void onApplicationStart(){
    Registry registry = new Registry();
    registry.register(Cat.class, cat -> catProcessor.process(cat));
    registry.register(Dog.class, dog -> dogProcessor.process(dog));
    registry.registerFallback(Animal.class, ani -> animalProcessor.process(ani));
}

Then later, you could fetch the registry, and the method that does the processing.

void whenNeeded(Animal animal){
    Registry registry = fetchRegistrySomehow();
    registry.for(animal.getClass()).apply(animal);
}

Edit: The implementation of registry is deliberately missing, as exact behavior differs depending on how you want to do the lookup, treat class hierarchies, when and if the registry should be sealed after some application started event.

like image 105
Ryan Leach Avatar answered Nov 05 '22 04:11

Ryan Leach


I think you can hide you approach into beautiful candy wrapper, e.g. using Enum or Class hierarchy in case of many overroden methods.

E.g. this is two Animal implementation with different methods name to get animal's name:

class Dog implements Animal {

    public String getDogName() {
        return "dog";
    }
}

class Cat implements Animal {

    public String getCatName() {
        return "cat";
    }
}

Then you can define an Enum, with Functions for each implementation:

enum AnimalRegistry {
    DOG(Dog.class, animal -> ((Dog)animal).getDogName()),
    CAT(Cat.class, animal -> ((Cat)animal).getCatName());

    private final Class<? extends Animal> cls;
    private final Function<Animal, String> getName;

    AnimalRegistry(Class<? extends Animal> cls, Function<Animal, String> getName) {
        this.cls = cls;
        this.getName = getName;
    }

    public final String getName(Animal animal) {
        return getName.apply(animal);
    }

    public static AnimalRegistry parseClass(Animal animal) {
        for (AnimalRegistry registry : values())
            if (registry.cls == animal.getClass())
                return registry;
        throw new RuntimeException("Unknown Animal implementation: " + animal.getClass().getSimpleName());    
    }
}

Finally, your client code could look like:

Animal animal = ...;
String animalName = AnimalRegistry.parseClass(animal).getName(animal);

Repeat, that if you have e.g. more that 2 methods to implement, Enum becomes not so comfortable to use; then you can switch into class hierarhy and do exactly like in enum (do not forget, the in JVM each constant in Enum is a different implementatino of Enum interface).

P.S. Your approach is not so bad, it is pretty usefule in many cases:

private void process(Animal animal) {
    if (animal instanceof Cat)
        process((Cat)animal);
    else if (animal instanceof Dog)
        process((Dog)animal);
    else
        System.out.println("unsopported animal");
}

private void process(Cat cat) {
    cat.getCatName();
}

private void process(Dog dog) {
    dog.getDogName();
}
like image 39
oleg.cherednik Avatar answered Nov 05 '22 03:11

oleg.cherednik