Use a strategy to implement the diverging behaviour in the animals:
https://refactoring.guru/design-patterns/strategy
I added a Java scratch file () that you can peruse for further insights.
If someone other finds the code teaches wrongly, please let me know so I will myself be reviewed.
class Scratch {
public static void main(String[] args) {
// abstract Animal
Animal abstractCat = new Cat("abstract and a bit of red");
abstractCat.move();
// concrete Animal
Cat concreteCat = new Cat("concrete and tigerlike");
concreteCat.move();
// change bug behaviour (move()) whilst program is running by assigning new AnimalBehaviour interface.
new Bug("iridium").move().setAnimalBehaviour(new AnimalMoveBehaviour()).move();
//as AnimalBehaviour is an interface resp. a SAM, we can define its functionality via a lambda.
new Bug(5, 3, "chaotic", animal -> System.out.println(animal.color +" colored animal moves with " +animal.legs + " legs and flies with " +animal.wings + "wings")).move();
}
}
// define what functionality / behaviour an object should fulfil.
interface AnimalBehaviour {
void move(Animal animal);
}
// one could think of a ProtoAnimal, which then is divided into Flying and Walking, or both, or even Swimming.
abstract class Animal {
int legs, wings;
String color;
// inject behaviour (which is basically a contract) into class
AnimalBehaviour animalBehaviour;
public Animal(int legs, int wings, String color, AnimalBehaviour animalBehaviour) {
this.legs = legs;
this.color = color;
this.wings = wings;
this.animalBehaviour = animalBehaviour;
}
// setAnimalBehaviour enables changing the behaviour during runtime.
// see main method for example.
public Animal setAnimalBehaviour(AnimalBehaviour animalBehaviour) {
this.animalBehaviour = animalBehaviour;
// fluent interface to return instance so method-chaining becomes possible.
return this;
}
// delegate Animal's move method (AnimalBehaviour) to interface.
public Animal move(){
this.animalBehaviour.move(this);
// fluent interface to return instance so method-chaining becomes possible.
return this;
}
}
class Cat extends Animal {
// standard cat
public Cat(String color) {
super(4, 0, color, new AnimalMoveBehaviour());
}
// cat whose parameters can be passed during instantiation
public Cat(int legs, int wings, String color, AnimalBehaviour ab) {
super(legs, wings, color, ab);
}
}
class Bug extends Animal {
// standard bug
public Bug(String color) {
super(6, 4, color, new AnimalFlyBehaviour());
}
// bug whose parameters can be passed during instantiation
public Bug(int legs, int wings, String color, AnimalBehaviour ab) {
super(legs, wings, color, ab);
}
}
class AnimalMoveBehaviour implements AnimalBehaviour {
@Override
public void move(Animal animal) {
System.out.println(animal.color + " colored animal is moving with " + animal.legs + " legs");
}
}
class AnimalFlyBehaviour implements AnimalBehaviour {
@Override
public void move(Animal animal) {
System.out.println(animal.color + " colored animal is flying with " + animal.legs + " wings");
}
}