relationship.
Well, is-a yields a correct program only if it preserves LSP. Although
there are some cases where LSP is not preserved: if I make Integer and
String subtypes of AdditiveType, Integer addition is symmetric but
String addition/concatenation is not although math tells us that '+'
is reserved for symmetric operations.
From the examples you gave, I see that, for you, is-a is thought in
terms of interface and polymorphism, not in terms of subtyping.
Now, if I have a class Mammal:
class Mammal
{
public:
Mammal(unsigned nb_breast):nb_breast(nb_breast){}
unsigned nbBreast()const{ return nb_breast; };
protected:
unsigned nb_breast;
};
If I define cat is-a mammal:
class Cat: public Mammal
{
public:
enum Type{ /* type of cat */};
Cat(Type type): Mammal(catType2NbBreast(type)){}
void mastectomy(unsigned nb_breast_removed)
{
assert( nb_breast_removed <= nb_breast);
nb_breast -= nb_breast_removed;
}
};
Here, I have a is-a relationship without talking about LSP or
polymorphism.
As I said elsewhere, the fact that C++ implements (dynamic)
polymorphism in terms of inheritance doesn't help. Well, it couldn't
do it another way, now, could it ?