I'll throw in my contribution even though the question has already been answered well enough. Thing is, programmers tend to think of architecture in a taxonomic as opposed to architecturally. That is to say, programmers will tend to look for classifications of Objects rather than structures of Objects. This can probably be attributed to real life: when you meet a person, you think of them as
- A woman
- A white woman
- A pretty white woman
- A pretty white woman in a nice dress.
This is triggered within about a second when our brain processes our view of the woman. You would end up with a class hierarchy:
class Woman {}
class WhiteWoman extends Woman {}
class PrettyWhiteWoman extends WhiteWoman {}
class NicelyDressedPrettyWhiteWoman extends PrettyWhiteWoman {}
Now, it is fairly obvious that this architecture is very naive, but we probably make these design mistakes where the hierarchy is not so easily defined.
In order to write more reusable code, we have to fight our brain's desire to classify and instead attempt to compose. "Composition over inheritance" as they say.
In your example, you are thinking that a horse "is a" mammal, and a horse "is a" method of transportation (i.e. extension). In fact, it is more correct to think that a horse "has a" mammalian biology and a horse "has a"n ability to act as transport. Thus, a Horse should implement the Mammalable and Transporter interface contracts.
Now we come to a point of contention about how some hierarchies should be structured. For example, say that on our farm we have a lot of FourLeggedAnimals, each of which exhibits some behavior that is identical to their four-legged brethren. On our farm, we also have Omnivores. Omnivores also also exhibit similar behavior. Problem is that some animals are no more Omnivores than they are four-legged. It does not make sense for FourLeggedAnimal to extend from Omnivore or vice versa. An example of this contention is if we have both humans and pigs on our farm.
Instead, we have to rethink our architecture. We should think about our objects from the inside out rather than the top down. Instead of thinking "what defines a horse," we should think "what composes a horse." In the context of our farm, this definition of horse may make the most sense (php-based pseudo-code):
interface Mammal {
function growHair();
}
interface Transporter {
function loadUp(bulk);
function moveOut(destination);
}
class FourLeggedAnimal {
public function walk() {
echo "Going for a walk";
}
}
class Omnivore {
public function eat(Food food) {
echo "Enjoying some food";
}
}
class Herbivore {
public function eat(Meat food) {
echo "Blech!";
}
public function eat(Veggies food) {
echo "Nom nom nom";
}
}
class Horse implements Mammal, Transporter {
private FourLeggedAnimal;
private Herbivore;
//Mammal/Transporter methods omitted
public function __construct(FourLeggedAnimal fla, Herbivore h) {
this.FourLeggedAnimal = fla;
this.Herbivore = h;
}
public function eat(Food food) {
this.Herbivore.eat(food);
}
public function walk() {
this.FourLeggedAnimal.walk();
}
}
This example is very naive and could probably use some improvement, but I hope the concept is there. Since we have moved away from the "is a" mentality, our architecture is more SOLID. Horse dependencies are inverted, we don't have to concern ourselves with Liskov substitution violations, etc.
The only downside to this architecture, and it is a big one, is the duplicated code. Do we really have to have an entire walk() method for Horse just to have it exhibit its FourLeggedAnimal behavior? If we can find a way around this inherent problem, there'd be no stopping us.