Practice Lab 8 — Appointment slot with validated booking¶
Difficulty: Intermediate · Estimated time: 4 hours · Module 4: Encapsulation and Access Control
The rule that makes this lab hard: two objects must agree. Validation in one object is a check; validation across two is a design.
At a glance¶
| Difficulty | Intermediate |
| Estimated time | 4 hours |
| Module | Module 4 — Encapsulation and Access Control |
| Concepts practised | Invariants spanning objects, validation at the boundary, information hiding, interface design, refusing versus correcting, const correctness |
| Prerequisite | Practice Lab 7 |
| Class design | yours — the requirements name the rules, not the classes |
Scenario¶
A clinic books appointments by slot: a doctor, a date, a start time, a duration in minutes, and a capacity. A slot is free, partly booked, or full, and a waiting list exists for full slots.
Three rules come from the clinic's manager, and she has seen each of them broken by the previous system:
- An appointment is never double-booked: the same patient never holds two appointments in the same slot.
- A slot is never overbooked: the booked count never exceeds the capacity.
- A booking is never made in the past — and "the past" is today's date, which the program must be told rather than reading from the clock inside a rule.
The previous system let the receptionist set a slot's booked count directly, which is how rule 2 was broken. That setter is the thing you are not allowed to write.
Problem statement¶
Design and implement a booking model for one doctor's day, with at least:
- something that represents a slot — doctor, date, start time, duration, capacity, and the bookings made in it;
- something that represents a booking — a patient and the slot it is in;
- today supplied from outside, never read inside a rule.
Operations the clinic needs:
- Create a slot, refusing a non-positive duration or capacity.
- Book a patient into a slot, refusing when the slot is full, when the patient already holds a booking in it, or when the slot is in the past.
- Cancel a booking, and offer the freed place to the waiting list before anyone else.
- Join a waiting list for a full slot, refusing a duplicate entry.
- Report a slot's free places and whether it is full.
- Print a doctor's day: every slot in time order, with its bookings.
- Print a patient's appointments, in time order, across slots.
You choose the classes, their names and their operations. The requirements are the contract; the design is yours. Write it down before writing code.
Learning objectives¶
By the end of this lab you will be able to:
- Design several classes whose rules interact, and say which class owns which rule.
- Express a rule that spans two objects, and enforce it in exactly one place.
- Take a contextual value (today) as a parameter instead of reading it inside a rule, and explain what that makes testable.
- Decide between refusing an operation and correcting the input, and defend the choice.
- Distinguish a waiting list's order rule from a slot's capacity rule, and say why they live in different places.
Functional requirements¶
- A booking is always inside exactly one slot, and a booking never outlives its slot.
- The booked count is never set directly by any caller; it changes only through booking and cancelling.
- Booking refuses: a full slot; a patient already in that slot; a slot on a past date; an unknown slot.
- Each refusal names its reason, and names the numbers where a limit was involved.
- Cancelling a booking that does not exist is refused. Cancelling frees exactly one place.
- A freed place goes to the waiting list first, in the order the entries were added, and only then to a new caller.
- A patient cannot appear twice on the same waiting list.
- A slot's capacity is never above a documented maximum (choose one and document it).
- Printing a doctor's day is time-ordered and stable for equal times by a documented rule.
- Every query is
const, and aconstday can be printed.
Constraints¶
- A header/source split; build with an explicit file list.
- No setter for the booked count or the waiting list. This is the lab's central constraint.
- Today is a parameter of every operation that needs it.
time(),std::chrono::system_clock::now()andstd::timemust not appear in your code — search for them and record the result. - No inheritance, no templates, no smart pointers.
- Dates as
intinYYYYMMDDform, times as minutes since midnight. Document both. - No warnings.
Starter task¶
Write design.md with:
- Your classes, each with a one-sentence responsibility.
- For each of the three manager rules: the class that owns it, and the single operation that enforces it.
- The waiting list's order rule, stated as a sentence, and where it is enforced.
- Where "today" enters your program, and every operation that takes it.
- The one thing you deliberately chose not to model, and why it is not needed yet.
Then write the declarations only, and check that no public operation can set a booked count.
Implementation tasks¶
- Implement the slot and booking types, with the refusals.
- Implement the waiting list and the offering rule.
- Implement the two reports (a doctor's day, a patient's appointments).
- Write
mainto walk through the test cases in order, printing a line per action. - Run the boundary cases, especially the same-day and next-day ones.
- Record in
design.mdwhat changed in your design while implementing — there is always something.
Test cases¶
| # | Action | Expected result |
|---|---|---|
| 1 | Create a slot with capacity 0 | Refused |
| 2 | Book two different patients into a capacity-2 slot | Both accepted; free places 0 |
| 3 | Book a third patient | Refused: full |
| 4 | Book the same patient twice into one slot | Refused: already booked |
| 5 | Book into a slot dated before today | Refused: in the past |
| 6 | Book into a slot dated exactly today | Accepted — today is not the past |
| 7 | Join the waiting list twice with the same patient | Second entry refused |
| 8 | Cancel a booking in a full slot with a waiting list | The first waiting patient is booked automatically; free places stay 0 |
| 9 | Cancel a booking that does not exist | Refused |
| 10 | Print the day with three slots added out of order | Printed in time order |
| 11 | Print a patient's appointments across two slots | In time order |
| 12 | Attempt to set a booked count from main |
Does not compile; the message is recorded |
Boundary cases to run deliberately: capacity 1; a booking on the last place; cancelling the only booking in an empty slot; two slots at the same time; a waiting list of one.
Expected behaviour¶
today = 20260414
slot A 20260414 09:00 30 min capacity 2 booked 0/2 free 2
book A patient 101 -> accepted booked 1/2 free 1
book A patient 102 -> accepted booked 2/2 free 0
book A patient 103 -> refused: slot is full (2 of 2)
join A patient 103 -> waiting, position 1
cancel A patient 102 -> booked 1/2; patient 103 booked from the waiting list, free 0
book A patient 101 -> refused: patient 101 already has a booking in this slot
book B 20260413 09:00 -> refused: 20260413 is before today (20260414)
patient 101 has 1 appointment: 20260414 09:00
Extension challenge¶
Add doctor availability windows: a doctor is available from a time to a time, and a slot must lie entirely inside one.
Record in design.md:
- Where the window rule lives, and how many operations consult it.
- Whether a window belongs to the doctor, to the day, or to a separate scheduler — with the cost of each choice in one sentence.
- What happens to existing slots when a window shrinks, and which of your rules decides it.
- The change cost of the extension: how many files, and how many operations.
Debugging challenge¶
The clinic manages to double-book one patient:
bool book(int patient, int slot_index, int today) {
if (slots_[slot_index].date < today) {
std::cout << "refused: in the past\n";
return false;
}
if (slots_[slot_index].booked >= slots_[slot_index].capacity) {
std::cout << "refused: full\n";
return false;
}
slots_[slot_index].patients[slots_[slot_index].booked] = patient;
++slots_[slot_index].booked;
return true;
}
Reproduce it — two bookings by the same patient in one slot — and answer:
- Which rule is missing, and which of the manager's three rules is it?
- Why does the slot's capacity check not catch it?
- Where should the duplicate check live: in the booking operation, in the slot, or in the patient? Answer with what each placement implies about who knows about whom.
- If the patient also has appointments in other slots, does your fix still work? Say what you checked.
Reflection questions¶
- Which of your classes owns the "is this in the past" rule, and which operation passes today in? What would break if the rule read the clock itself?
- The waiting list is offered a freed place first. What happens if the waiting patient is no longer eligible, and how did your design discover that?
- You were not allowed to write a setter for the booked count. Which caller would have wanted one, and what does your design offer them instead?
- Two rules — capacity and duplicate booking — are enforced in different places. Is that a design smell? Justify your answer with what each place knows.
- Which of your operations returns a collection by value, and how big can that copy get?
- If the clinic added a second doctor whose slots share a waiting list, which classes would change and how many operations would follow?
Completion checklist¶
- [ ] It builds from a documented command with
-Wall -Wextra -Werrorand no warnings - [ ]
design.mdnames a class and a single operation for each of the three manager rules - [ ] No setter for a booked count or a waiting list — proved by an attempted call that does not compile
- [ ] No clock read inside a rule —
grepfortime(,now()andstd::chronoand record the result - [ ] Booking refuses all four invalid cases, each with its own reason
- [ ] A freed place goes to the waiting list first — demonstrated with output
- [ ] A
constday can be printed - [ ] All twelve test cases run, with output recorded
- [ ] Boundary cases run, including a slot dated exactly today
- [ ] The debugging challenge was explained before being fixed
- [ ] I can say which class knows about which other class, and why that direction is right
Related: Lab index · Module 4 — Encapsulation · Previous: Practice Lab 7 · Next: Practice Lab 9 — Money type with full operator support