One Interface Per Behavior, and What Override Is Actually For
Why a Flyable list only lets you call fly(), how a second behavior like eating gets its own interface, and the real purpose of method overriding.
The previous post landed on Flyable, an interface that lets a list mean "every bird that can fly." Worth asking directly: why would anyone actually need that list in the first place?
The Only Reason to Want That List
There's really one answer: to call fly() on every bird in it. That's it. There's no other operation that a list of flying birds buys you.
std::vector<Flyable*> fliers = { &crow, &sparrow, &pigeon };
for (Flyable* bird : fliers) {
bird->fly();
}Look at what the compiler actually allows inside that loop. bird has the type Flyable, so the only thing callable on it is fly(), nothing else. The compiler checks the declared type of a variable, not what the underlying object might also be capable of. That restriction is the entire value of typing the list as Flyable[] instead of Bird[]: it guarantees, at compile time, that nothing in this loop can accidentally reach for a method that isn't guaranteed to exist.
A Second Behavior Gets Its Own Interface
Now add a second independent behavior: some birds can eat, some can't. Follow the exact same move that fixed flying.
First, eat() comes out of Bird entirely, since it's no longer something every bird shares.
class Bird {
public:
std::string name;
std::string color;
std::string type;
int age;
void walk() { /* still shared by every bird */ }
virtual ~Bird() = default;
};
class Eater {
public:
virtual void eat() = 0;
virtual ~Eater() = default;
};Then, only the birds that actually eat implement it. Say Crow and Pigeon can eat, but Sparrow can't.
class Crow : public Bird, public Flyable, public Eater {
public:
void fly() override { /* ... */ }
void eat() override { /* ... */ }
};
class Pigeon : public Bird, public Flyable, public Eater {
public:
void fly() override { /* ... */ }
void eat() override { /* ... */ }
};
class Sparrow : public Bird, public Flyable {
public:
void fly() override { /* ... */ }
};
class Penguin : public Bird {
// implements neither
};A class can implement more than one interface at once, picking up exactly the behaviors it actually has, and nothing it doesn't. Crow and Pigeon both fly and eat. Sparrow only flies. Penguin does neither. Each class's interface list is an honest, exact description of what it can do.
The "Birds That Fly and Eat" Trap
A natural next question: what if some code needs a list of birds that can both fly and eat?
That's not a sign you need a combined interface. It's a sign the surrounding code is already doing too much. A method that depends on two behaviors at once has two reasons to change, the same Single Responsibility Principle problem, just in a new spot.
The fix is splitting that logic into two operations, one that only needs Flyable, one that only needs Eater. Let the calling code combine the results if it truly needs both. If a single object's dual capability really does need checking directly, a plain canFly / canEat flag is a more honest tool than gluing two unrelated interfaces together.
This still isn't a scaling problem. Ten independent behaviors need ten small interfaces, not 1,024 classes. That gap is why interfaces, not classes, were the right tool from the start.
What Overriding Is Actually For
One more idea worth pulling out, because it explains why all of this works: overriding a method should never make it do a different thing. It should make it do the same thing, differently.
fly() means "get this bird airborne," no matter which bird implements it. A crow's fly() and a sparrow's fly() do that differently, wingbeat pattern, altitude, speed, but both keep that same meaning. The moment an override changes what the method means, it's no longer an override. It's a broken promise, the same failure mode as UsbPort.connect() meaning something unrelated to Database.connect().
That single rule is what makes Flyable[] trustworthy. Every object in that list flies in its own way, but never means something else by it.
Keep reading