Introduction
This lesson builds one small program that uses most of the course: a library that lends items. Read the code first, then step through it.
Knowing the features is not the same as designing with them. This lesson reads one complete program and asks why each part is built the way it is, so that you can make the same decisions in your own programs.
How it works
The complete program is in the Example section below, where you can run it and step through it. These notes explain the decisions behind it, and are easiest to follow with the code open beside them.
How it is designed
Itemis abstract. Every item has a title and can be borrowed or returned, but each kind describes itself differently and has its own loan period, sodescribe()andloanDays()are pure virtual.BookandDVDfill them in.- Encapsulation.
borrowedis private. The only way to change it isborrow()andgiveBack(), which enforce the rules, and throw an exception when someone breaks them. - The library owns its items.
Librarykeeps avector<Item*>and deletes the items in its destructor. Copying aLibrarywould make two owners, so its copy constructor and assignment are deleted (= delete): the compiler refuses to copy it. - Polymorphism.
list()loops overItem *and callsdescribe(); each item prints itself in its own way. - Errors are exceptions.
lendthrows for an unknown title or an item that is already out.maincatches them and carries on.
Design questions to ask
| Question | Answer here |
|---|---|
| What are the things (nouns)? | items, books, DVDs, the library: the classes |
| What can each do (verbs)? | borrow, give back, describe: the member functions |
| What is shared, what differs? | shared in Item; differences in virtual functions |
| Who owns what? | the library owns the items and deletes them |
| What can go wrong? | bad titles, double loans: exceptions |
Try changing it: add a Magazine class with a 3-day loan, or a member function that counts the items that are out.
Syntax
The design uses features from the whole course. The ones to look for:
virtual string describe() const = 0; // each kind fills this in
Library(const Library&) = delete; // no copies: one owner
throw invalid_argument("no item called " + title);
for (Item* it : items) delete it; // the owner cleans up
Example
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
using namespace std;
class Item {
string title;
bool borrowed;
public:
Item(string t) : title(t), borrowed(false) {}
virtual ~Item() {}
string getTitle() const { return title; }
bool isBorrowed() const { return borrowed; }
void borrow() {
if (borrowed) throw runtime_error(title + " is already borrowed");
borrowed = true;
}
void giveBack() { borrowed = false; }
virtual string describe() const = 0;
virtual int loanDays() const = 0;
};
class Book : public Item {
string author;
public:
Book(string t, string a) : Item(t), author(a) {}
string describe() const override { return getTitle() + " by " + author; }
int loanDays() const override { return 14; }
};
class DVD : public Item {
int minutes;
public:
DVD(string t, int m) : Item(t), minutes(m) {}
string describe() const override { return getTitle() + " (" + to_string(minutes) + " min)"; }
int loanDays() const override { return 7; }
};
class Library {
vector<Item*> items;
public:
Library() {}
Library(const Library&) = delete;
Library& operator=(const Library&) = delete;
~Library() {
for (Item* it : items) delete it;
}
void add(Item* it) { items.push_back(it); }
Item& find(const string& title) {
for (Item* it : items)
if (it->getTitle() == title) return *it;
throw invalid_argument("no item called " + title);
}
void lend(const string& title) {
Item& it = find(title);
it.borrow();
cout << "lent " << it.describe() << " for " << it.loanDays() << " days" << endl;
}
void list() const {
for (Item* it : items) cout << (it->isBorrowed() ? "[out] " : "[in] ") << it->describe() << endl;
}
};
int main() {
Library lib;
lib.add(new Book("Clean Code", "Martin"));
lib.add(new DVD("Cosmos", 60));
lib.lend("Cosmos");
try {
lib.lend("Cosmos");
} catch (const exception& e) {
cout << "sorry: " << e.what() << endl;
}
try {
lib.lend("Dune");
} catch (const exception& e) {
cout << "sorry: " << e.what() << endl;
}
lib.list();
return 0;
}The program models a small library that lends books and DVDs. main adds two items, lends one, tries two loans that break the rules, and lists the items.
Line by line
Itemholds the title and whether the item is out, both private, and its pure virtual functions make it abstract.borrowthrows aruntime_errorif the item is already out, so the rule cannot be broken quietly.BookandDVDfill indescribeandloanDays, with 14 and 7 days.Libraryowns its items: it deletes them in its destructor, and copying it is deleted with= delete.findreturns a reference to an item, or throwsinvalid_argumentfor an unknown title.- In
main, the second loan ofCosmosand the loan ofDuneboth throw, and eachcatchprints a message. listprints each item, marked[out]or[in], through the virtualdescribe.
Expected result
lent Cosmos (60 min) for 7 days
sorry: Cosmos is already borrowed
sorry: no item called Dune
[in] Clean Code by Martin
[out] Cosmos (60 min)
In the real world
Real library, shop and booking systems are designed the same way, with many more classes. Writing down the classes, their responsibilities and who owns what before coding is the first step of object-oriented design in industry.
Common mistakes
- Putting every feature into one large class, so that nothing can change without touching everything.
- Leaving it unclear who deletes an object, which leads to leaks or double deletes.
- Using inheritance for "has a" relationships, such as making
Libraryderive fromItem.
Best practices
- Sketch the classes and their relationships before writing code.
- Give each class one clear responsibility.
- Make ownership obvious, and delete copying when it would create two owners.
Summary
Object-oriented design turns a problem's nouns into classes and its verbs into member functions. Abstract classes capture what varies, encapsulation protects each object's rules, one object owns each resource, and exceptions report broken rules.
Quick check
Key takeaways
- Nouns suggest classes, and verbs suggest member functions.
- Shared behaviour goes in a base class, and differences in virtual functions.
- Exactly one object should own each resource.
Practice problems for this lesson
My notes
Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.