Reputation: 53
I have an abstract class Animal
, with two extending classes, Dog
and Cat
.
public abstract class Animal {
...
}
public class Dog extends Animal {
...
}
public class Cat extends Animal {
...
}
In another class, I have an ArrayList<Animal> animals
that contains instances of both Cat
and Dog
.
In the class with the ArrayList<Animal>
, I want to be able to overload a method doSomething()
with either Dog d
or Cat c
as the parameters, but still be able to call them from the ArrayList of Animals. As follows:
public void aMethod(){
ArrayList<Animals> animals = getTheAnimals();
for (Animal a : animals){
doSomething(a);
}
}
public void doSomething(Dog a){
//a is an object of type dog
}
public void doSomething(Cat a){
//a is an object of type cat
}
Essentially, each method is acting as a 'selector' for which type
of animal is being received by the method.
When trying as above, I get the following compilation error:
error: no suitable method found for doSomething(Animal)
I know I could use something like instanceOf
or a.getClass().equals(type)
, but I have read that this is bad practice.
This question differs slightly from this other question, as I want two separate methods, each with a different parameter.
EDIT: I am going to avoid using the Visitor Pattern as I don't think it really fits well into my actual implementation. Will move the doSomething() method into each class, and refactor to ensure this makes logical sense (each class performing actions that it should be responsible for). Thanks.
Upvotes: 4
Views: 5554
Reputation: 1033
Move method into Abstract
class so each implementation has to make its own version of the method.
public abstract class Animal {
public abstract void doSomething();
}
public class Dog extends Animal {
public void doSomething(){
System.out.println("Bark");
}
}
public class Cat extends Animal {
public void doSomething(){
System.out.println("Meow");
}
}
Then in the other Class you mentioned above you can do the following:
public void vocalize(){
List<Animal> animals = getTheAnimals();
for (Animal animal : animals){
animal.doSomething();
}
}
Upvotes: 2
Reputation: 11
There's an important rule in Java that defines most of how Inheritance, Interface and Abstract classes work - A superclass reference variable can hold a subclass object.Hence, there are a few ways to overcome your error.
Dog
and Cat
classes are concrete classes for the Animal
abstract class, then you can simply define one doSomething(Animal animal)
and the method will work.doSomething
method generic. Such as
public <T> void doSomething(<T extends Animal> animal)
Upvotes: 1
Reputation: 5336
Yes instanceof and getclass would make your class dependent upon specific types and using such practices are not too good.
But by having methods accepting Dog
or Cat
you again end up depending upon the concrete specific implementation.
Better way is Define method in Animal
interface performAction()
. Provide implementation in both Dog
and Cat
. Now iterate your List of Animals and just invoke performAction()
and polymorphically as per actual instance either Dog or Cat implemented method would be called. Later even if there is another implementation of Animal added in code you need not modify your code.
This way you would be having Dog related logic in Dog class, Cat related logic in Cat class and not in any outside different class. This will help in respecting OCP and SRP (Open close and Single Responsibility principles).
Also in case you wish to perform behavior later and your case is such that you know your implementations are fixed and want the using code to inject behavior later then I would recommend Visitor
pattern, but still I feel this should not be abused, (design pattern are often not used but abused) sometime we force a pattern when not needed. Visitor pattern uses polymorphic to dispatch the first call to specific instance from where second dispatch calls the overloaded methods in class implementing the Visitor interface. (name can be different, I have mentioned Visitor to imply class implementing the concept of visitor) . Go for Visitor pattern like implementation only if needed and normal above polymorphic based approach do not fit the situation.
Upvotes: 1
Reputation: 999
Instead of iterating in your client method and calling doSomething(), keep doSomething() as a method in your abstract class, that way you can change the way you want in Cat or Dog.
public void aMethod(){
ArrayList<Animals> animals = getTheAnimals();
for (Animal a : animals){
a.doSomething();
}
}
abstract Animal {
doSomething();
}
Dog extends Animal {
doSomething() {
//Dog stuff
}
}
Cat extends Animal {
doSomething() {
//cat stuff
}
}
Upvotes: 2
Reputation: 131546
You need a double dispatch : a runtime type provided by java + a runtime parameter type that you have to implement. The visitor pattern is a way to do that.
Introduce a Visitable
interface, implements it in Animal
to make it accept a visitor.
Then introduce a Visitor. You can implement it with or without interface according to your requirement.
Simple version (by coupling the visitor to the current class) that keeps the actual logic of your code :
public interface Visitable{
void accept(MyClassWithSomeAnimals myClassWithSomeAnimals);
}
public abstract class Animal implements Visitable{
...
}
public class Dog extends Animal {
...
void accept(MyClassWithSomeAnimals myClassWithSomeAnimals){
myClassWithSomeAnimals.doSomething(this);
}
}
public class Cat extends Animal {
...
void accept(MyClassWithSomeAnimals myClassWithSomeAnimals){
myClassWithSomeAnimals.doSomething(this);
}
}
And update MyClassWithSomeAnimals
in this way :
public void aMethod(){
ArrayList<Animals> animals = getTheAnimals();
for (Animal a : animals){
a.accept(this);
}
}
This solution works but it has a drawback. It exposes MyClassWithSomeAnimals
to Animal
subclasses while These need only to have a way to invoke the visitor method, not any public methods.
So a improvement would be to introduce a Visitor
interface to limit to doSomething()
what Animal know of the visitor :
public interface Visitor{
void doSomething(Dog dog);
void doSomething(Cat cat);
}
So make MyClassWithSomeAnimals
implement it and change Visitable class/concrete classes to use it :
public interface Visitable{
void accept(Visitor visitor);
}
public class Dog extends Animal {
...
void accept(Visitor visitor){
visitor.doSomething(this);
}
}
public class Cat extends Animal {
...
void accept(Visitor visitor){
visitor.doSomething(this);
}
}
Upvotes: 1