TL;DR - Only use inheritance if the subtype can perfectly replace the base type. In addition, reduce the responsibilities of its classes. Use composition to join several isolated responsibilities into one object (i.e. add functionality).
The rest of the answer is based on practical examples. For a more conceptual (and shorter) answer, including theoretical background, see the answer of utluiz. And the response of the carlosrafaelgn presents a rather interesting counterpoint.
When not to use inheritance?
The decision between using or not inheriting should be based on Principle of Liskov’s Substitution, which says: "where a base class object can be used a subclass object must also be able to be used". Maintaining this principle becomes complicated in complex inheritance relationships, as in the example below:
class Animal { }
class Cachorro extends Animal { }
class Gato extends Animal { }
class AbrigoAnimais {
Animal obterAnimal() { }
void adicionarAnimal(Animal a) { }
}
class AbrigoCachorros extends AbrigoAnimais {
Cachorro obterAnimal() { } // OK
void adicionarAnimal(Cachorro c) { } // É overload, não overrride
}
AbrigoAnimais canil = new AbrigoCachorros();
canil.adicionarAnimal(new Gato()); // Deveria ser válido, pelo princípio da substituição
The same is observed in cases where the subclass is more restricted than the superclass:
class Retangulo {
void atribuirLados(int altura, int largura) { }
}
class Quadrado extends Retangulo { }
new Quadrado().atribuirLados(10, 20); // Deveria ser válido, pelo princípio da substituição
In addition - not related to this principle, but also relevant - there is the problem of what to do in the presence of multiple inheritance:
interface Voador { }
class Veiculo { }
class Aviao extends Veiculo implements Voador { }
class Passaro extends Animal implements Voador { }
// Como reaproveitar a funcionalidade de Voador?
Or in cases where the interface is conflicting:
interface Foo {
String metodo();
}
interface Bar {
int metodo();
}
class Baz implements Foo, Bar {
// Não é possivel, pois o que "metodo" vai retornar?
}
If your model doesn’t have any of these problems, then go ahead and use inheritance! But most of the time (in my experience) at least one of these problems is present. In addition, the programmer’s motivation is often simply to "reuse code" - he doesn’t have a clear taxonomy where the inheritance relationship is justified, he only uses it because he thinks he has to use it. It is in these cases that the composition becomes a preferable alternative.
How to solve using composition?
In the first case, this can be done by placing in the base type only the methods that meet the substitution principle:
interface AbrigoAnimais {
Animal obterAnimal();
}
class ImplAbrigoAnimais<T extends Animal> implements AbrigoAnimais {
T obterAnimal() { } // OK, tipo de retorno covariante
void adicionarAnimal(T animal) { } // Não está presente na interface
}
class AbrigoCachorros implements AbrigoAnimais {
ImplAbrigoAnimais<Cachorro> abrigo = new ImplAbrigoAnimais<Cachorro>();
Cachorro obterAnimal() { return abrigo.obterAnimal() }
void adicionarAnimal(Cachorro cachorro) { abrigo.adicionarAnimal(cachorro); }
}
AbrigoAnimais canil = new AbrigoCachorros();
canil.obterAnimal(); // OK
Same thing in the second (note that it is possible to support both reading and writing):
interface Retangular {
void atribuirLargura(int largura);
void atribuirAltura(int altura);
}
class Retangulo implements Retangular {
void atribuirLargura(int largura) { }
void atribuirAltura(int altura) { }
void atribuirLados(int largura, int altura) { } // Não está na interface
}
class Quadrado implements Retangular {
Retangulo quadrado = new Retangulo();
void atribuirLargura(int largura) {
atribuirLados(largura);
}
void atribuirAltura(int altura) {
atribuirLados(altura);
}
void atribuirLados(int valor) { // Não está na interface
quadrado.atribuirLargura(valor);
quadrado.atribuirAltura(valor);
}
}
Separation of Responsibilities
Finally, I will demonstrate how the separation of responsibilities can greatly help to model a complex set of entities:
interface ObjetoPosicionado { }
interface Animal extends ObjetoPosicionado { }
interface Veiculo extends ObjetoPosicionado { }
class ImplObjetoPosicionado implements ObjetoPosicionado {
int x;
int y;
int z;
}
class ImplAnimal implements Animal {
ObjetoPosicionado impl;
int fome;
}
class ImplVeiculo implements Veiculo {
ObjetoPosicionado impl;
List<Pessoa> passageiros;
}
Here each class takes care of a specific aspect: one controls where the object is in space, another controls the specific attributes of the animal making use of of the previous object (as a Animal
is a ObjetoPosicionado
), and another controls the specific vehicle attributes (idem). With single responsibilities, one can reuse them in a particular class by taking only what is needed, and not wasting space:
class Cavalo implements Animal, Veiculo {
ImplAnimal ia;
ImplVeiculo iv;
public Cavalo() {
ObjetoPosicionado op = new ObjetoPosicionado(); // Uma única cópia...
ia = new ImplAnimal(op); // ...é utilizada aqui...
iv = new ImplVeiculo(op); // ...e aqui.
}
// Realiza a interface de ObjetoPosicionado
void deslocar(int x, int y, int z) {
ia.deslocar(x,y,z); // Ou iv - dá no mesmo, pois ambos têm a mesma "impl"
}
// Realiza a interface de Animal
void comer() {
ia.comer();
}
// Realiza a interface de Veiculo
void adicionarPassageiro(Pessoa p) {
iv.adicionarPassageiro(p);
}
}
But it’s not much code to write?
Yes! If language syntax doesn’t help, you’d have to do a lot of gymnastics to create models of this type, walk that effort "pay" later (a clean API and efficient code). And what would be the solution? Leave the "purity" aside and end the inheritance at once:
class Cavalo {
Animal comoAnimal() { return ia; }
Veiculo comoVeiculo() { return iv; }
}
Cavalo c = new Cavalo();
//Animal a = c;
Animal a = c.comoAnimal();
That is: back to the "strangeness" of the original question, but gain all the benefits of the use of composition except the overhead of the extra code to make this possible. At the end of the day, it’s still heritage that you’re doing - but without using its syntax/semantics, but rather that of composition.
Is the ideal?
No. The ideal would be for the computer to take good care of the "confusion" that is the way the human brain categorizes things ("Birds fly. Ostriches are birds, only they don’t fly. Planes also fly, but they don’t flap their wings. Rockets fly, and they don’t even have wings! Ducks are birds, and they also swim. Boats don’t swim, but that’s about it... Planes, rockets and boats are vehicles. Missiles look like rockets, but they don’t carry passengers.").
In the absence of this, all that remains is to create models with desirable characteristics, such as type safety. It aims to ensure that "similar" entities can be treated as one type - by substituting according to the principle quoted at the beginning of the answer. If you don’t have this security, what good is having an object that may or may not fulfil the class contract?
In the end, what interests the programmer is that a given object has certain desirable functionality. That is, conceptually um carro é um veículo
, but pragmatically um objeto carro possui a funcionalidade de um objeto veículo
.
Note: my answer was very long, but I can not explain it using fewer words... If anyone has a more succinct answer - and perhaps even more complete, because what I wrote was only a result of my personal experience - they will be very welcome!
– mgibsonbr
very explanatory answer, now it makes more sense with the real examples of misuse of inheritance, in relation to "strangeness" of using composition in place of inheritance I think it is worth for all the benefits I get and with the advantage of using "Generics" as in your example gets better yet, very good explanation! Tomorrow I’ll give you another 50pts for the answer.
– Tuyoshi Vinicius