1

In Java, I want to implement concrete behavior in an abstract method but am having difficulty.

My issue is I cannot get the specific data to perform operations without using casting or instanceofs. I am looking for help to better design my example (eventually a vampire-game) and to learn a new way of doing it.

Example: People and Vampires. People eat NormalFood (cake, etc) that give sugar/carbs/proteins. Vampires eat BloodFood that give blood. There are going to be many types of food and these are just 2 examples. Both are Persons.

The Person has an eat method which takes type Food. It checks to see if the type is allowed (ie: Vampires can't eat NormalFood so nothing happens). My issue is after the check, how do we turn Food into BloodFood or NormalFood without using casts or instanceofs. The only way I can see this is by using some sort of getX(key) methods, ie: getInt("Sugar"), getFloat("Blood"), etc.

What else can I do?

Here are my code examples:

    public abstract class Person
    {
        ArrayList<Class<? extends Food>> allowedFoods = new ArrayList();

        public void eat(Food food)
        {
            System.out.println("------------------");
            System.out.println("Person: " + this);
            System.out.println(" Food: " + food);

            boolean has = allowedFoods.contains(food.getClass());

            System.out.println("  allowed: " + has);

            if(has == false)
                return;

            eatHelper(food);

            System.out.println("------------------");
        }

        abstract protected void eatHelper ( Food food );
    }

    public class HumanPerson extends Person
    {
        int fat;
        int sugar;
        int protein;

        public HumanPerson()
        {
            allowedFoods.add(NormalFood.class);
        }

        @Override
        protected void eatHelper ( Food food )
        {
            //How to get 'fat'/etc from the food without casting or using instanceof/etc.
            //Can eventually accept other types of food instead of NormalFood, ie: PoisonedFood
        }

    }


    public class VampirePerson extends Person
    {
        private int blood;

        public VampirePerson()
        {
            allowedFoods.add(BloodFood.class);
        }

        @Override
        protected void eatHelper ( Food food )
        {
            //How to get 'blood' from the food without casting or using instanceof/etc.
            //Can eventually accept other types of food instead of BloodFood
        }

    }


public class NormalFood extends Food
{
    int sugar = 100;
}


public class BloodFood extends Food
{
    int blood = 50;
}


public class Food
{

}

You can see how the different foods have varying values and the behavior would vary. For example, there would be an ElderVampirePerson who would gain more blood per point, so their eat method should handle that situation.

Here is the main method:

Person person = new HumanPerson();
Person vampire = new VampirePerson();
Food foodOne = new NormalFood();
Food foodTwo = new BloodFood();

System.out.println("\nHuman is eating:");
person.eat(foodOne);
person.eat(foodTwo);

System.out.println("\nVampire is eating:");
vampire.eat(foodOne);
vampire.eat(foodTwo);
  • "how do we turn Food into BloodFood or NormalFood without using casts or instanceofs" Why? I mean, inside the abstract class `Person` this is not a good idea. However in `VampirePerson` and `HumanPerson` you exactly know the type that this person allows ... so you can cast it. – Seelenvirtuose Jul 28 '19 at 15:58
  • Besides that: This is an opionion-based question. Additionally, design questions should be asked on https://codereview.stackexchange.com/. – Seelenvirtuose Jul 28 '19 at 15:59
  • @Seelenvirtuose Should I simply repost this question there? – dustOfNothing Jul 28 '19 at 16:30
  • @Seelenvirtuose What I'm trying to figure out it is if the casting in the concrete method is the best way to do this. I run into this issue a lot and think I'm doing the design incorrectly when I'm always casting in the concrete implementations. Thanks! – dustOfNothing Jul 28 '19 at 16:35
  • 1
    You should consider generics here. – Seelenvirtuose Jul 28 '19 at 16:37
  • @Seelenvirtuose: Useless suggestion. It is solvable without generics. – mentallurg Jul 28 '19 at 16:40
  • @Seelenvirtuose: This is NOT a design question. Have you understood the question? – mentallurg Jul 28 '19 at 16:43
  • @dustOfNothing: See the solution below. – mentallurg Jul 28 '19 at 16:47
  • @mentallurg your solution is exactly the same as doing an `instance of` – Arnaud Claudel Jul 28 '19 at 16:52
  • @Arnaud Claudel: No. For instanceof you need to know the class names. Where as isAssignableFrom works without knowing class names. – mentallurg Jul 28 '19 at 16:57
  • But it's still a poor OOP design – Arnaud Claudel Jul 28 '19 at 16:58
  • Have you looked into the [visitor pattern](https://stackoverflow.com/questions/29458676/how-to-avoid-instanceof-when-implementing-factory-design-pattern/29459571#29459571)? – Vince Jul 28 '19 at 19:42
  • @dustOfNothing I reply here to your comment on my answser because I want to suggest a new approach. If you want the food to be easily parameterized, you can externalize it into a config file, like `foodName,protein=100,blood=200 etc. Then you would have to parse it on startup and load dynamically the properties of the food into a HashMap for example. – Arnaud Claudel Jul 29 '19 at 07:33

4 Answers4

0

Here is a solution that could work using generics, if you only have one kind of food.

abstract class Person<F extends Food> {

    abstract void eat(F food);
}

class NormalPerson extends Person<NormalFood> {

    @Override
    void eat(NormalFood food) { }
}

class Vampire extends Person<BloodFood> {

    @Override
    void eat(BloodFood food) { }
}

If you need to handle multiple kinds of food, then consider using interfaces.

interface Eater<F extends Food> {
    void eat(F food);
}

class NormalPerson implements Eater<NormalFood>, Eater<BloodFood> {
    @Override
    void eat(NormalFood f) {}

    @Override
    void eat(BloodFood f) {}
}

class Vampire implements Eater<BloodFood> {
    @Override
    void eat(BloodFood f) {}
}
Arnaud Claudel
  • 3,000
  • 19
  • 25
  • what if person could eat also BloodFood? – m.antkowicz Jul 28 '19 at 16:50
  • In its example, there is only one kind of food. If there're multiples kind, then an interface like `Eater` would also work – Arnaud Claudel Jul 28 '19 at 16:50
  • allowed foods is an `ArrayList` so nope - in this example you have direct information that it is not only one kind – m.antkowicz Jul 28 '19 at 16:52
  • @ArnaudClaudel Thx for the reply. If there is a better design that exists where allowedFoods is removed (or used differently/etc), then I'm all for it. I was planning on allowing the game to be modded, so allowing for easy extension would certainly be a must (but I haven't got that far as you saw in my example). – dustOfNothing Jul 29 '19 at 01:42
  • This wouldn't compile due to type erasure - you can't implement the same interface even if the type arguments differ, such as `class SomeType implements Consumer, Consumer`, so you'd have to create new interfaces such as `BloodEater extends Eater`. – Vince Jul 31 '19 at 18:14
0

I've spent a lot of time with what @rghome suggested and think I came up with a somewhat usable solution. It isn't perfect and has a few gotchas (my parameterize/generics are giving me some trouble). I look for anyone to make comments on this solution. Side Note: Should I open a new question with my proposed solution? I don't post on SO that much.

The idea was to have the Person decide how the food interacts with themselves, where the Food provides data. They can do this by using FoodPairs which in turn gets the Data from the allowed input Food. It also can handle the case where ElderVampires get x2 Blood when compared to normal Vampires. On a related note, I feel as if I can accomplish some of this in a better way using interfaces. I also think this can be abstracted even more wrt to food and other objects/actions to impact the Person.

Finally, this solution has the foundations for modding - modders have flexibility and could create their new classes and register their FoodPairs and behavior without needing to have access the base class.

This solution has many smaller classes (I'm OK with this) but I will only post the Vampire solution (without imports). If you wish to see the ElderVampire classes, I can post those too.

public abstract class Food {
    public abstract int getFoodData(GetFoodData getData); //rawType warning
   }

public class BloodFood extends Food {
    public int blood = 50;

    @Override
    public int getFoodData(GetFoodData getData) { //rawType warning
    return getData.getData(this); //type safety warning
    }
}

public class BadBloodFood extends Food {
    public int blood = -75;

    @Override
    public int getFoodData(GetFoodData getData) {
    return getData.getData(this);
    }
}

FoodPairs:

public abstract class FoodPair<P> {
    private HashMap<Class<? extends Food>, GetFoodData<? extends Food>> getDataMap = new HashMap<>();

    public GetFoodData<? extends Food> getData(Food food) {
    return getDataMap.get(food.getClass());
    }

    protected void register(Class<? extends Food> class1, GetFoodData getData) {
    getDataMap.put(class1, getData);
    }

    abstract public void digest(P p);
}

public class GetBloodPoints extends GetFoodData<BloodFood> {
    @Override
    public int getData(BloodFood food) {
    return food.blood;
    }
}
public class GetBadBloodPoints extends GetFoodData<BadBloodFood> {
    @Override
    public int getData(BadBloodFood food) {
    return food.blood;
    }
}

Class that controls what and how the Food behaves for the Vampire:

public class VampireBloodFoodPair extends FoodPair<VampirePerson> {
    private int blood;

    public VampireBloodFoodPair() {
    register(BloodFood.class, new GetBloodPoints());
    register(BadBloodFood.class, new GetBadBloodPoints());
    }

    public void construct(Food food) {
    blood = food.getFoodData(getData(food));
    }

    @Override
    public void digest(VampirePerson vampirePerson) {
    vampirePerson.blood += blood;
    }
}

Vampire:

public class VampirePerson extends Person {
    int blood;

    VampireBloodFoodPair vbfp = new VampireBloodFoodPair();

    public VampirePerson() {
    allowedFoods.add(BloodFood.class);
    allowedFoods.add(BadBloodFood.class);
    }

    @Override
    protected void eatHelper(Food food) {
    System.out.println("-----------------------------------------------------------------");
    System.out.println(this.getClass().getSimpleName() + " EATING THIS FOOD: " + food);
    System.out.println("-----------------------------------------------------------------");

    System.out.println("BEFORE vbfp Digest Food Blood: " + blood);
    vbfp.construct(food);
    vbfp.digest(this);
    System.out.println("After vbfp Digest Food Blood: " + blood);
    }
}

Test Case:

Food foodOne = new NormalFood();
Food foodTwo = new BloodFood();
Food foodThree = new BadBloodFood();

System.out.println("\nVampire is eating:");
vampire.eat(foodOne);
vampire.eat(foodTwo);
vampire.eat(foodThree);

And output:

Vampire is eating:
------------------
Person: VampirePerson
 Food: NormalFood@2aae9190
  allowed: false
------------------
Person: VampirePerson
 Food: BloodFood@2f333739
  allowed: true
-----------------------------------------------------------------
VampirePerson EATING THIS FOOD: BloodFood@2f333739
-----------------------------------------------------------------
BEFORE vbfp Digest Food Blood: 0
After vbfp Digest Food Blood: 50
------------------
------------------
Person: VampirePerson
 Food: BadBloodFood@77468bd9
  allowed: true
-----------------------------------------------------------------
VampirePerson EATING THIS FOOD: BadBloodFood@77468bd9
-----------------------------------------------------------------
BEFORE vbfp Digest Food Blood: 50
After vbfp Digest Food Blood: -25
  • @rghome Here is my attempt based on what you suggested. I tried to @ you in the answer post but don't think it worked. Ha, hopefully this worked. – dustOfNothing Aug 06 '19 at 20:02
-1

Use isAssignableFrom.

You can implement it in your base class like following:

protected boolean isAllowedFood(Food food){
  for (Food allowedFood: allowedFoods){
    if (allowedFood.getClass().isAssignableFrom(food.getClass)){
      return true;
    }
  }
  return false;
}
mentallurg
  • 4,967
  • 5
  • 28
  • 36
-2

This is indeed a difficult problem, but not unique. The solution is to use callbacks (this design technique is used in the Vistor pattern, although I haven't used the exact convention for that here).

The big problem you have is that you have (presumably) a lot of person types and a lot of food types and everyone can potentially try to eat anything and there is a large combination of outcomes.

My first proposal is that you add various abstract methods to Person:

class Person {

    abstract void digestBlood(int amount);
    abstract void digestPoison(int strength, int amount);
    abstract void digestSugar(int amount);
    abstract void digestProtein(int amount);
etc.

Actually, you don't need to have a different method for each type of food. You can try and categorise the foods by their content (e.g, sugar, protein, poison, etc) or some other grouping.

You may also want to have a default implementation (either instead of the abstract method or via a default method in an interface).

and also the concrete method:

   void eat(Food food) {
       food.digest(person);
   }

You can then add a method to Food:

abstract void digest(Person person);

and override for various food types:

class Blood extends Food {
    int amount = 10;
    void digest(Person person) {
        person.digestBlood(amount);
    }

 class Meat {
    void digest(Person person) {
        person.digestProtein(10);
        person.digestBlood(1);
    }

  etc.

Then you can override the digest methods in the various types of person:

class Vampire extends Person {
    int health;
    void digestBlood(int amount) {
        health += amount;
    }
    void digestPoision(int strength, int amount) {
       // no effect
}

This is really only a start. You can go a step further like putting each digest??? method in its own class (e.g., Digester) and have each person type contain an array of Digesters depending on what they can digest. This gets rid of all of the digest methods, but you need more classes.

Another technique where you have lots of similar classes with similar properties (e.g., Food types) is to use an enumeration and have some of the properties of the food as fields of the enumerated class.

I hope this gives you some ideas.

rghome
  • 8,529
  • 8
  • 43
  • 62
  • 2
    This is a poor design because it forces specialized methods that only one derived class needs onto the base class and with that on all derived classes. What does `digestBlood` do on a person? Just nothing? Great, maybe I called it by mistake and now I can spend hours debugging. Would it throw an unchecked exception? That breaks the Person contract, which states that I can call this `digestWhatever` on *all* Person instances. – Robert Jul 28 '19 at 18:38
  • @Robert This is a common design pattern that is used a lot (a variant of the Visitor pattern). To answer your first question: you can provide default implementations in the base class that don't do anything. You don't throw an exception. But you have to handle the case where somebody consumes (or try to consume) some blood. As I said in the last paragraph, you can get rid of the individual methods by introducing another class, but I didn't want to make the answer too long. I have expanded on the answer a bit so that maybe you can understand it better. – rghome Jul 28 '19 at 19:21
  • 2
    Sorry but your solution has nothing to do with a Visitor pattern – m.antkowicz Jul 28 '19 at 22:18
  • @rghome While pondering this, I was thinking of breaking it up into classes to do the processing but wasn't sure if that was viable (and would complicate my question in general). Would this Digester class feature allow for easy extension by modders (who won't have access to the Person source, for example)? – dustOfNothing Jul 29 '19 at 01:45
  • @dustOfNothing you have two options: 1) make it data driven. Have a few classes but lots of data (maybe initialised from a file) 2) Use classes and polymorphism. (Or I suppose you can use both). For modding, the data driven approach is going to be easier (as the modders don't have to write Java and you don't need to worry about malicious classes), but you will have to have a lot of hardwired behaviour. – rghome Jul 29 '19 at 07:55
  • @rghome Thanks for the continued dialog. I have another game and did Option 1 - there was a set of integer and float variables where I stored a lot of the data and then had a Type variable that decided how it actually behaved and that class performed the operations. It worked well but was hardwired and restricted like you said. So I wanted to try to think on different and better ways to do this. I then came up with my current issue. Even though this got downvoted I will still investigate this line of thinking. – dustOfNothing Jul 29 '19 at 17:22