samedi 28 mars 2020

How to add missing methods to classes in a type hierarchy to allow for uniform processing?

I'm using Java 8 / Java 11. I have a type hierarchy (basically dtos or Java Beans) like

public abstract class Animal {
  public abstract String getName();
  public abstract int getAge();
}

And some imeplementations providing additional properties:

public class Dog extends Animal {

  // implementation of abstract methods from base class animal

  // additional properties
  public String getSound() {
    return "woof";
  }
}

public class Dog extends Animal {

  // implementation of abstract methods from base class animal

  // additional properties
  public String getSound() {
    return "miaow";
  }
}

public class Fish extends Animal {
  // implementation of abstract methods from base class animal

  // no implementaion for "getSound()"
}

Now, I'd like to process a Collection of Animals in a uniform way, e.g.

animals.forEach(x -> {
  System.out.println(x.getName());  // works
  System.out.println(x.getSound();  // doesn't work, as Fish is missing the method
});

I was wondering, what would be a good way to implement the "missing" methods assuming that they should return a default value like "n/a" for a String.

One obvious way would be to move all the missing methods to the base class and either declare them abstract or provide a default implementation. But I'd like to have them more separate, i.e. making clear which properties were added for the "uniform processing". Another way would be to introduce a helper class using instance of to determine, if the method is missing:

public class AnimalHelper {

  public static String getSoundOrDefault(Animal animal) {
    if (animal instanceof Dog) {
        return ((Dog)animal).getSound();
    }

    if (animal instanceof Cat) {
        return ((Cat)animal).getSound();
    }
    return "n/a";
  }
}

which then gets called with an Animal:

System.out.println(AnimalHelper.getSoundOrDefault(animal));

This works, but the caller must now which methods to call on Animal directly and for which methods to use the helper.

Another solution, I came up with the adding an interface AnimalAdapter using the Java 8 feature of default implementation:

public interface AnimalAdapter {

  default String getSoundOrDefault() {
    return "n/a";
  }
}

And adding it to the Animal class:

public abstract class Animal implements AnimalAdapter {
...

which results in adding the getSoundOrDefault() method in Dog and Cat, but not Fish:

public class Dog extends Animal {
  ...
  @Override
  public String getSoundOrDefault() {
    return getSound();
  }
}

(likewise in Cat).

Any comments on the above considerations or other ideas would be highly appreciated.

Aucun commentaire:

Enregistrer un commentaire