Refactor Java using inheritance between subclass and superclass
I would like to refactor some code using inheritance between my superclass and subclasses.
These are my classes:
public class Animal {
int a;
int b;
int c;
}
public class Dog extends Animal {
int d;
int e;
}
public class Cat extends Animal {
int f;
int g;
}
This is my current code:
ArrayList<Animal> listAnimal = new ArrayList();
if (condition) {
Dog dog = new Dog();
dog.setA(..);
dog.setB(..);
dog.setC(..);
dog.setD(..);
dog.setE(..);
listAnimal.add(dog);
} else {
Cat cat = new Cat();
cat.setA(..);
cat.setB(..);
cat.setC(..);
cat.setF(..);
cat.setG(..);
listAnimal.add(cat);
}
I would like refractor the commons attributes.
I know this doesn't work. I could use constructor in subclass for create an instance with Animal class in parameter, but it doesn't use inheritance... I would like something like that:
Animal animal = new Animal();
animal.setA(..);
animal.setB(..);
animal.setC(..);
if (condition) {
Dog anim = (Dog) animal; //I know it doesn't work
anim.setD(..);
anim.setE(..);
} else {
Cat anim = (Cat) animal; //I know it doesn't work
anim.setF(..);
anim.setG(..);
}
listAnimal.add(anim);
Is there any solution to achieve something like this ?
Your idea to have a variable of type Animal is good. But you also have to make sure to use the right constructor:
Animal animal; // define a variable for whatever animal we will create
if (condition) {
Dog dog = new Dog(); // create a new Dog using the Dog constructor
dog.setD(..);
dog.setE(..);
animal = dog; // let both variables, animal and dog point to the new dog
} else {
Cat cat = new Cat();
cat.setF(..);
cat.setG(..);
animal = cat;
}
animal.setA(..); // modify either cat or dog using the animal methods
animal.setB(..);
animal.setC(..);
listAnimal.add(animal);
Hint: If an Animal is always either Cat or Dog consider making Animal abstract. Then the compiler will automatically complain whenever you try to do new Animal().
The process of constructing either a cat or dog is complex since a lot of fields are involved. That is a good case for the builder pattern.
My idea is to write a builder for each type and organise relationships between them. It could be composition or inheritance.
AnimalBuilder constructs a general Animal object and manages the a, b, c fields
CatBuilder takes an AnimalBuilder(or extends it) and continues building a Cat object managing the f, g fields
DogBuilder takes an AnimalBuilder (or extends it) and continues building a Dog object managing the d, e fields
If you don't want to create builders, consider introducing a static factory method with a meaningful name for each subclass:
Animal animal = condition ? Dog.withDE(4, 5) : Cat.withFG(6, 7);
// populate animal's a, b, c
listAnimal.add(animal);
It would simplify construction and make it less verbose and more readable.
Answer
One way of doing it is to add the proper constructors to your classes. Look below:
public class Animal {
int a, b, c;
public Animal(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
}
public class Dog extends Animal {
int d, e;
public Dog(int a, int b, int c, int d, int e) {
super(a, b, c);
this.d = d;
this.e = e;
}
}
public class Cat extends Animal {
int f, g;
public Cat(int a, int b, int c, int f, int g) {
super(a, b, c);
this.f = f;
this.g = g;
}
}
Now, to instantiate the objects, you can do like below:
ArrayList<Animal> listAnimal = new ArrayList();
//sample values
int a = 10;
int b = 5;
int c = 20;
if(condition) {
listAnimal.add(new Dog(a, b, c, 9, 11));
//created and added a dog with e = 9 and f = 11
}
else {
listAnimal.add(new Cat(a, b, c, 2, 6));
//created and added a cat with f = 2 and g = 6
}
This is the method I would use in this case. It keeps the code cleaner and more readable by avoiding that tons of "set" methods. Note that super() is a call to the superclass' (Animal in this case) constructor.
Bonus
If you don't plan to create instances of the class Animal, you should declare it as being abstract. Abstract classes can't be instanciated, but can be subclassed and can contain abstract methods. Those methods are declared without a body, meaning that all the children classes must provide its own implementation of it. Here is an example:
public abstract class Animal {
//...
//all animals must eat, but each animal has its own eating behaviour
public abstract void eat();
}
public class Dog {
//...
@Override
public void eat() {
//describe the eating behaviour for dogs
}
}
Now you can call eat() for any and every animal! In the preceding example, with the list of animals, you would be able to do like below:
for(Animal animal: listAnimal) {
animal.eat();
}
Consider making your classes immutable (Effective Java 3rd Edition Item 17). If all parameters are required use a constructor or a static factory method (Effective Java 3rd Edition Item 1). If there are required and optional parameters use the builder pattern (Effective Java 3rd Edition Item 2).
I would consider a dynamic lookup/registration of capabilities/features: Flying/Swimming.
It is the question whether this fits your usage: instead of Flying & Swimming take Bird and Fish.
It depends whether the properties added are exclusive (Dog/Cat) or additive (Flying/Swimming/Mammal/Insect/EggLaying/...). The latter is more for a lookup using a map.
interface Fish { boolean inSaltyWater(); }
interface Bird { int wingSpan(); setWingSpan(int span); }
Animal animal = ...
Optional<Fish> fish = animal.as(Fish.class);
fish.ifPresent(f -> System.out.println(f.inSaltyWater()? "seafish" : "riverfish"));
Optional<Bird> bird = animal.as(Bird.class);
bird.ifPresent(b-> b.setWingSpan(6));
Animal need not implement any interface, but you can look up (lookup or maybe as) capabilities. This is extendible in the future, dynamic: can change at run-time.
Implementation as
private Map<Class<?>, ?> map = new HashMap<>();
public <T> Optional<T> as(Class<T> type) {
return Optional.ofNullable(type.cast(map.get(type)));
}
<S> void register(Class<S> type, S instance) {
map.put(type, instance);
}
The implementation does a safe dynamic cast, as register ensures the safe filling of (key, value) entries.
Animal flipper = new Animal();
flipper.register(new Fish() {
@Override
public boolean inSaltyWater() { return true; }
});
These are my classes:
public class Animal {
int a;
int b;
int c;
}
public class Dog extends Animal {
int d;
int e;
}
public class Cat extends Animal {
int f;
int g;
}
This is my current code:
ArrayList<Animal> listAnimal = new ArrayList();
if (condition) {
Dog dog = new Dog();
dog.setA(..);
dog.setB(..);
dog.setC(..);
dog.setD(..);
dog.setE(..);
listAnimal.add(dog);
} else {
Cat cat = new Cat();
cat.setA(..);
cat.setB(..);
cat.setC(..);
cat.setF(..);
cat.setG(..);
listAnimal.add(cat);
}
I would like refractor the commons attributes.
I know this doesn't work. I could use constructor in subclass for create an instance with Animal class in parameter, but it doesn't use inheritance... I would like something like that:
Animal animal = new Animal();
animal.setA(..);
animal.setB(..);
animal.setC(..);
if (condition) {
Dog anim = (Dog) animal; //I know it doesn't work
anim.setD(..);
anim.setE(..);
} else {
Cat anim = (Cat) animal; //I know it doesn't work
anim.setF(..);
anim.setG(..);
}
listAnimal.add(anim);
Is there any solution to achieve something like this ?
Your idea to have a variable of type Animal is good. But you also have to make sure to use the right constructor:
Animal animal; // define a variable for whatever animal we will create
if (condition) {
Dog dog = new Dog(); // create a new Dog using the Dog constructor
dog.setD(..);
dog.setE(..);
animal = dog; // let both variables, animal and dog point to the new dog
} else {
Cat cat = new Cat();
cat.setF(..);
cat.setG(..);
animal = cat;
}
animal.setA(..); // modify either cat or dog using the animal methods
animal.setB(..);
animal.setC(..);
listAnimal.add(animal);
Hint: If an Animal is always either Cat or Dog consider making Animal abstract. Then the compiler will automatically complain whenever you try to do new Animal().
The process of constructing either a cat or dog is complex since a lot of fields are involved. That is a good case for the builder pattern.
My idea is to write a builder for each type and organise relationships between them. It could be composition or inheritance.
AnimalBuilder constructs a general Animal object and manages the a, b, c fields
CatBuilder takes an AnimalBuilder(or extends it) and continues building a Cat object managing the f, g fields
DogBuilder takes an AnimalBuilder (or extends it) and continues building a Dog object managing the d, e fields
If you don't want to create builders, consider introducing a static factory method with a meaningful name for each subclass:
Animal animal = condition ? Dog.withDE(4, 5) : Cat.withFG(6, 7);
// populate animal's a, b, c
listAnimal.add(animal);
It would simplify construction and make it less verbose and more readable.
Answer
One way of doing it is to add the proper constructors to your classes. Look below:
public class Animal {
int a, b, c;
public Animal(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
}
public class Dog extends Animal {
int d, e;
public Dog(int a, int b, int c, int d, int e) {
super(a, b, c);
this.d = d;
this.e = e;
}
}
public class Cat extends Animal {
int f, g;
public Cat(int a, int b, int c, int f, int g) {
super(a, b, c);
this.f = f;
this.g = g;
}
}
Now, to instantiate the objects, you can do like below:
ArrayList<Animal> listAnimal = new ArrayList();
//sample values
int a = 10;
int b = 5;
int c = 20;
if(condition) {
listAnimal.add(new Dog(a, b, c, 9, 11));
//created and added a dog with e = 9 and f = 11
}
else {
listAnimal.add(new Cat(a, b, c, 2, 6));
//created and added a cat with f = 2 and g = 6
}
This is the method I would use in this case. It keeps the code cleaner and more readable by avoiding that tons of "set" methods. Note that super() is a call to the superclass' (Animal in this case) constructor.
Bonus
If you don't plan to create instances of the class Animal, you should declare it as being abstract. Abstract classes can't be instanciated, but can be subclassed and can contain abstract methods. Those methods are declared without a body, meaning that all the children classes must provide its own implementation of it. Here is an example:
public abstract class Animal {
//...
//all animals must eat, but each animal has its own eating behaviour
public abstract void eat();
}
public class Dog {
//...
@Override
public void eat() {
//describe the eating behaviour for dogs
}
}
Now you can call eat() for any and every animal! In the preceding example, with the list of animals, you would be able to do like below:
for(Animal animal: listAnimal) {
animal.eat();
}
Consider making your classes immutable (Effective Java 3rd Edition Item 17). If all parameters are required use a constructor or a static factory method (Effective Java 3rd Edition Item 1). If there are required and optional parameters use the builder pattern (Effective Java 3rd Edition Item 2).
I would consider a dynamic lookup/registration of capabilities/features: Flying/Swimming.
It is the question whether this fits your usage: instead of Flying & Swimming take Bird and Fish.
It depends whether the properties added are exclusive (Dog/Cat) or additive (Flying/Swimming/Mammal/Insect/EggLaying/...). The latter is more for a lookup using a map.
interface Fish { boolean inSaltyWater(); }
interface Bird { int wingSpan(); setWingSpan(int span); }
Animal animal = ...
Optional<Fish> fish = animal.as(Fish.class);
fish.ifPresent(f -> System.out.println(f.inSaltyWater()? "seafish" : "riverfish"));
Optional<Bird> bird = animal.as(Bird.class);
bird.ifPresent(b-> b.setWingSpan(6));
Animal need not implement any interface, but you can look up (lookup or maybe as) capabilities. This is extendible in the future, dynamic: can change at run-time.
Implementation as
private Map<Class<?>, ?> map = new HashMap<>();
public <T> Optional<T> as(Class<T> type) {
return Optional.ofNullable(type.cast(map.get(type)));
}
<S> void register(Class<S> type, S instance) {
map.put(type, instance);
}
The implementation does a safe dynamic cast, as register ensures the safe filling of (key, value) entries.
Animal flipper = new Animal();
flipper.register(new Fish() {
@Override
public boolean inSaltyWater() { return true; }
});
Comments
Post a Comment