Inheritance: pull up duplicated behavior
Write shared behavior once in a superclass, so every subclass gets each change.
Watch the narrated video
Every step
Problem
Steps 1–3Refactor
Steps 4–6Payoff
Steps 7–8C++ bonus
Step 9Recap
Steps 10–11The code: before and after
Before
class Shipment {
public:
virtual ~Shipment() = default;
virtual int price() const = 0;
};
class DomesticShipment : public Shipment {
std::string tracking;
int weightKg;
public:
DomesticShipment(const std::string& tracking, int weightKg)
: tracking(tracking), weightKg(weightKg) {}
int price() const override { return 5 + weightKg; }
std::string label() const {
return std::format("{} | {} kg | ${}",
tracking, weightKg, price());
}
};
class InternationalShipment : public Shipment {
std::string tracking;
int weightKg;
public:
InternationalShipment(const std::string& tracking, int weightKg)
: tracking(tracking), weightKg(weightKg) {}
int price() const override { return 20 + weightKg * 4; }
std::string label() const {
return std::format("{} | {} kg | ${}",
tracking, weightKg, price());
}
};
int main() {
DomesticShipment home("DM-1", 2);
InternationalShipment abroad("IN-7", 2);
std::cout << home.label() << '\n'; // DM-1 | 2 kg | $7
std::cout << abroad.label() << '\n'; // IN-7 | 2 kg | $28
}After
class Shipment {
protected:
std::string tracking;
int weightKg;
private:
bool fragile = false;
public:
Shipment(const std::string& tracking, int weightKg)
: tracking(tracking), weightKg(weightKg) {}
virtual ~Shipment() = default;
virtual int price() const = 0;
void markFragile() { fragile = true; }
std::string label() const {
std::string suffix = fragile ? " | FRAGILE" : "";
return std::format("{} | {} kg | ${}{}",
tracking, weightKg, price(), suffix);
}
};
class DomesticShipment : public Shipment {
public:
using Shipment::Shipment;
int price() const override { return 5 + weightKg; }
};
class InternationalShipment : public Shipment {
public:
using Shipment::Shipment;
int price() const override { return 20 + weightKg * 4; }
};
int main() {
DomesticShipment home("DM-1", 2);
InternationalShipment abroad("IN-7", 2);
abroad.markFragile();
std::cout << home.label() << '\n'; // DM-1 | 2 kg | $7
std::cout << abroad.label() << '\n'; // IN-7 | 2 kg | $28 | FRAGILE
}Transcript, step by step
Problem
Step 1: A courier's two shipments Before
A parcel courier's dispatch desk works with two kinds of shipment. Shipment is the superclass, the class the others inherit from, and each subclass, a class that inherits from it, says what its parcel costs. Both subclasses also keep a tracking code and a weight, and both build a label from them. In main, the desk prints one label for a domestic parcel and one for an international one.
Step 2: Fragile parcels Code smell
The courier now needs the word fragile on the label of fragile parcels. The quick way adds a fragile member, a mark fragile method and a label suffix to Domestic Shipment, and then the same three again to International Shipment. Main marks the parcel going abroad, so only the second label ends in fragile.
Step 3: Two copies of everything Code smell
Duplicated code across subclasses is when two classes that share a superclass each write the same fields and methods on their own. You can see the fields here and here, and the label here and here. So every change to the label is made twice, and the day someone edits only one copy, a fragile parcel ships without the word on it. A third shipment type would copy all of it a third time.
Refactor
Step 4: Refactor first Back it out
Let's take the fragile flag out again. We'll refactor first, so that the shared parts move up into Shipment, and then we'll add the flag in one place.
Step 5: Fields and constructor move up Pull Up Constructor Body
First, the tracking code and the weight move up into Shipment, as protected members that every subclass can read. In C plus plus, a constructor's initializer list can only set the class's own members, so the constructor body moves up with them. Each subclass constructor becomes one call to Shipment's constructor, with the tracking code and the weight.
Step 6: One label Pull Up Method
Now the two label bodies are identical, so one copy moves up into Shipment, and both subclass copies are deleted. Label still works because it calls price, and each subclass still answers price its own way. This is inheritance, where a subclass gets the fields and methods of its superclass, so the shared parts are written once, in Shipment. What's left in each subclass is its constructor and its price. A superclass method that calls a step each subclass fills in, like label calling price, is the Template Method pattern.
Payoff
Step 7: Fragile, once Add the flag
Now the fragile flag comes back, and this time it goes into Shipment only. Shipment gains one private fragile member and one mark fragile method, and label adds the suffix once. Domestic and International Shipment didn't change, and main only gains one call that marks the parcel going abroad.
Step 8: Shared code lives once Payoff
The fields now exist once, here, and the label exists once, here. The fragile rule is written once too, so the two labels can't drift apart, and the run prints the word only on the parcel going abroad. It fits because both classes are shipments, each with a tracking code, a weight and a label, and only the price differs. Abstraction hid how a class stores its data, and inheritance shares what two classes both do.
C++ bonus
Step 9: Inherit the constructor C++ bonus
One copy is still left, because each subclass writes a constructor that only passes its two arguments on to Shipment. Since C plus plus eleven, a using declaration inherits the superclass constructors, so both forwarding constructors are deleted. Main still builds each parcel from a tracking code and a weight, and the two labels print the same lines as before.
Recap
Step 10: Where we started, where we ended Before / after
On the left, each subclass keeps its own fields, its own constructor and its own label. On the right, Shipment holds the fields, the constructor, the fragile flag and the label, each written once. A subclass like Domestic Shipment keeps one using declaration and its own price.
Step 11: When to reach for it Takeaways
Reach for it when two or more subclasses of one superclass repeat the same fields or the same method body. Skip it when two classes look alike today but will change for different reasons, because a superclass ties them together, so use Extract Class and delegate to it. You'll spot the need by the same method copied into sibling subclasses, and the same members and constructor lines in each. The standard stream buffer works this way, because a subclass writes overflow, and inherits the put methods that call it.