Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Having overload methods from one abstract method in JAVA

Given an abstract class

public abstract class AbstractStomach {
    public abstract void consume(Food food);
}

I want to have some concrete classes with different overloading methods:

public class FruitStomach extends AbstractStomach {
    public static class Apple extends Food {}
    public static class Orange extends Food {}
    @Override
    public void consume(Apple food) {...}
    @Override
    public void consume(Orange food) {...}
}

public class MeatStomach extends AbstractStomach {
    public static class Beef extends Food {}
    @Override
    public void consume(Beef food) {...}
}

The above doesn't work as it is forcing me to implement also the generic consume(Food) abstract method in each concrete class.

But I dont want the concrete classes to implement consume(Food) as that has been taken care of by their overloading variants and really, each concrete class should consume different and specific food only, not ANY food, so that

AbstractStomach stomach = new FruitStomach();
stomach.consume(new Beef());

should give an error.

If I remove the abstract method in the abstract class then there is no point to have them extending the abstract class as it provides no guidance to them what to implement.

How do you fix the above design so that each concrete classes can have their overloading methods yet they know there is an abstract method consume() to implement but do not need to implement consume(Food).

Edit


I can resolve the above without using overloading, e.g.

public class FruitStomach extends AbstractStomach {
    public static class Apple extends Food {}
    public static class Orange extends Food {}
    @Override
    public void consume(Food food) {
        if (food instanceof Apple) {
           ...
        } else if (food instanceof Orange) {
           ...
        } else {
           // throws an error
        }
    }
}

But this is not what I want. I would like to see a solution that uses method overloading.

like image 308
user1589188 Avatar asked Feb 01 '17 05:02

user1589188


4 Answers

Abstract methods in abstract classes are more than about "providing guidance what to implement". Abstract classes allow you to have objects that can be of any subclass of the abstract class. That is, you can have a method like

public void evaluateDigestion(AbstractStomach stomach) {
}

and you can call it with any kind of Stomach object, a FruitStomach or a MeatStomach or a DessertStomach or whatever. Inside evaluateDigestion, the method can call stomach.consume(someKindOfFood). Since this is the consume defined for the abstract class (at compile time, evaluateDigestion doesn't know what the class is), it is the consume that uses a Food parameter. Then, at run time, the call to consume will "dispatch" to the consume(Food food) method defined for the concrete class. That's why a consume with a Food parameter has to be defined for each concrete class. The dispatching call doesn't search through overloading methods with Apple, Orange, etc., parameters; Java doesn't do that. (So your statement that it "has been taken care of by the overloading variants" is incorrect; there is a big difference between overloading and overriding, which can be confusing to newcomers.)

So you do have to write

public void consume(Food food)

in each concrete class. A very clunky way to implement this would be

public void consume(Food food) {
    if (food instanceof Apple) {
        consume((Apple)food);  // calls the overloaded version because you've use a cast to tell Java how to view the object
    } else if (food instanceof Orange) {
        consume((Orange)food);
    } else {
        throw ... // some exception that occurs when you try to eat the wrong kind of food
    }
}

Better would be to define an abstract method in Food, assuming Food is abstract:

public abstract class Food {
    public void abstract consumedBy(AbstractStomach stomach);
}

Define concrete versions in the Apple and Orange, etc, classes, and then write consume like

public static void consume(Food food) {
    food.consumedBy(this);
}

However, this wouldn't prevent an attempt to get a FruitStomach to consume beef. You may have to use instanceof to make sure the food is compatible. (One possibility: define a class Fruit that extends Food, and make Apple and Orange subclasses of Fruit; then you can say food instanceof Fruit to verify that it's the right kind of food. There may be a better design pattern than this, though. I can't think of one offhand.)

like image 66
ajb Avatar answered Oct 11 '22 19:10

ajb


You could use generics to (partially) solve your problem:

AbstractStomach defines a type parameter T which declares what type of Food it consumes:

public abstract class AbstractStomach<T extends Food> {
    public abstract void consume(T food);
}

Then you can declare a food type Fruit and the corresponding FruitStomach:

public class Fruit extends Food {}
public class FruitStomach extends AbstractStomach<Fruit> {
    public static class Apple extends Fruit {}
    public static class Orange extends Fruit {}
    @Override
    public void consume(Fruit food) {}
}

and the similar classes Meat and MeatStomach:

public class Meat extends Food {}
public class MeatStomach extends AbstractStomach<Meat> {
    public static class Beef extends Meat {}
    @Override
    public void consume(Meat food) {}
}
like image 21
Thomas Kläger Avatar answered Oct 11 '22 19:10

Thomas Kläger


The above doesn't work as it is forcing me to implement also the generic consume(Food)

That's the right behavior. By contract in abstract class AbstractStomach, you agree that,

Anything which extends AbstractStomach will have a method consume(Food) (any food) and then in subclass you are forcing it to have only two types of food, thus bypassing the contract.

like image 1
mallaudin Avatar answered Oct 11 '22 19:10

mallaudin


The problem you're facing is that your abstract method signature expects a super class called Food to be passed into the consume-method. Every implementation of this abstract class (and therefore its abstract method) must override the same signature.

e.g.

   public FruitStomach extends AbstractStomach {
    public static class Apple extends Food {}
    public static class Orange extends Food {}
    @Override
    public void consume(Food food) {
      if (food instanceof Orange) {
        Orange orange = (Orange) food;
        // do smth. with orange     
      } else{
        // so smth with food
      }
    }

   public MeatStomach extends AbstractStomach {
      public static class Beef extends Food {}
      @Override
      public void consume(Food food) {
        Beef beef = (Beef) food;
        // do smth. with beef...
      }
    }

could work. Although this is not very clean code and you got no compile-time verification if your code is wired together correctly. But as you stated, if you call the wrong Stomach.consume()-method on the wrong input food, you might raise a ClassCastException, which you could handle.

like image 1
Kevin Böckler Avatar answered Oct 11 '22 20:10

Kevin Böckler