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.
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.)
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) {}
}
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 methodconsume(Food)
(any food) and then in subclass you are forcing it to have only two types of food, thus bypassing the contract.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With