Step 7 of 9
Composition over inheritance
Inheritance is powerful, but it's also the tightest coupling there is: a derived class depends on every detail of its base. For many relationships there's a simpler tool. Inheritance means is-a: a Truck is a Vehicle. For has-a relationships, give the class a member instead. That's called composition.
class Squad : public std::vector<Player> { }; // wrong: a squad isn't a vector
class Squad { std::vector<Player> members_; }; // right: a squad has members
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
class Playlist {
public:
bool add(const std::string& song) {
if (songs_.size() >= 3) return false; // rule: max 3
if (std::find(songs_.begin(), songs_.end(), song) != songs_.end()) return false;
songs_.push_back(song);
return true;
}
std::string summary() const {
std::string out;
for (const auto& s : songs_) out += "[" + s + "]";
return out;
}
private:
std::vector<std::string> songs_; // has-a: the playlist controls access
};
int main() {
Playlist p;
std::cout << p.add("a") << p.add("b") << p.add("a") << p.add("c") << p.add("d") << "\n";
std::cout << p.summary() << "\n";
}
11010
[a][b][c]
Why composition is the default in professional code
- The class controls its own interface. Inheriting from
std::vectorwould exposeerase,clear,push_backand everything else, letting callers break the "max 3, no duplicates" rules. - You can swap the member's type later (say, to a
std::set) without touching any caller. - Standard containers have no virtual destructor, so they aren't meant to be base classes at all.
Your task
Squad is the same shape as Playlist: add checks the size limit and duplicates, remove finds the name with std::find and erases it, and roster joins the names with ", " (watch out for the separator fencepost from the streams module).
Your task
Write class Squad that has a std::vector<std::string> of names, with a maximum size of 5:
bool add(const std::string& name): false if full or already in the squadbool remove(const std::string& name): false if not foundint size() const,std::string roster() const(names joined with", ", in join order)
Challenge 2
Write class Backpack that has a std::map<std::string, int> of item counts and a weight limit. bool add(const std::string& item, int qty) fails (changing nothing) if it would take the total over the limit or qty <= 0. bool remove(const std::string& item, int qty) fails if there aren't that many, and erases an item whose count reaches 0. int total() const and int count(const std::string& item) const.
Challenge 3
Composition also lets you swap behaviour at run time. A Bot has a std::unique_ptr<Strategy>. Write Aggressive (always "push") and Careful ("retreat" below 50 hp, otherwise "hold"), then Bot::set_strategy and Bot::act(int hp), which returns "idle" when the bot has no strategy.
Previous: Calling the base version, and protected Next: Casts, dynamic_cast and slicing