Object-Oriented Analysis and Design (OOAD)

Learning Objectives

Go from requirements and use cases to a domain model.

Find candidate classes by responsibilities, not just nouns; use CRC cards.

Map the model to interactions, states, interfaces and code; trace requirements.

Prerequisites: Modules 19–24.

Intuition and Definition

OOAD is the process of understanding a problem (analysis) and shaping a solution as collaborating objects (design):

Requirements & use cases — who wants what, and the main/alternative flows.

Domain analysis — vocabulary, rules and invariants (with domain experts).

Candidate classes — from nouns *and* from responsibilities and rules.

CRC cards — Class, Responsibilities, Collaborators — one card per candidate.

Domain model — class diagram of concepts, relationships and invariants.

Interactions — sequence diagrams for key use cases; assign each step to the object with the needed information ("information expert").

States — state machines for objects with lifecycles.

Interfaces & implementation mapping — ports for external systems, code skeletons, tests.

Traceability & change impact — which requirement is implemented where.

Why It Matters

Jumping straight to code tends to produce classes shaped like screens or database tables. A short, iterative analysis step produces classes shaped like the *rules* — which are what change least.

Syntax and Language Notes

OOAD is language-neutral; outputs are diagrams, CRC cards and tests. Keep artefacts lightweight and executable where possible (diagrams-as-code, tests as specifications).

Complete Example

Use case — "Park a vehicle" (parking lot). Main flow: driver arrives → system finds a free spot of the right size → ticket issued → barrier opens. Alternative flows: lot full; oversized vehicle; lost ticket at exit.

CRC cards: find a spot, issue tickets, close tickets Spot, Ticket, PricingPolicy know its size, know if occupied know vehicle, spot and entry time compute fee from duration

Notice the noun "barrier" became no class at all in this design (it is a device driven by the lot through an interface), while "pricing policy" — never mentioned as a noun by the driver — became one, because it is a rule that changes.

Traceability: each alternative flow from the use case maps to a code path and a test — "lot full" → park returns nullopt; "unknown ticket" → leave returns nullopt. When the pricing rule changes, only PricingPolicy and its tests are affected (change impact analysis).

#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <vector>

enum class Size { Small = 1, Medium = 2, Large = 3 };

struct Vehicle { std::string plate; Size size; };

class Spot {
    int id_; Size size_; bool occupied_ = false;
public:
    Spot(int id, Size s) : id_(id), size_(s) {}
    bool fits(const Vehicle& v) const { return !occupied_ && static_cast<int>(v.size) <= static_cast<int>(size_); }
    void occupy() { occupied_ = true; }
    void release() { occupied_ = false; }
    int id() const { return id_; }
};

struct Ticket { int number; std::string plate; int spotId; int entryMinute; };

class PricingPolicy {                       // a rule that changes -> its own class
public:
    int feeCents(int minutes) const { int hours = (minutes + 59) / 60; return hours <= 1 ? 200 : 200 + (hours - 1) * 150; }
};

class ParkingLot {
    std::vector<Spot> spots_;
    std::map<int, Ticket> open_;
    PricingPolicy pricing_;
    int nextTicket_ = 1;
public:
    explicit ParkingLot(std::vector<Spot> s) : spots_(std::move(s)) {}
    std::optional<Ticket> park(const Vehicle& v, int minute) {
        for (Spot& s : spots_)                       // smallest-first
…
bike -> spot 1, van -> spot 3, truck -> no space
van leaves after 155 min, fee 500 cents
truck retries -> spot 3
unknown ticket: rejected
sequenceDiagram
  participant D as Driver
  participant L as ParkingLot
  participant S as Spot
  participant P as PricingPolicy
  D->>L: park(vehicle)
  L->>S: fits(vehicle)?
  S-->>L: yes
  L->>S: occupy()
  L-->>D: Ticket
  D->>L: leave(ticket)
  L->>P: feeCents(minutes)
  P-->>L: fee
  L-->>D: fee

Step-by-Step Walkthrough

Responsibilities were assigned to the class with the information: Spot knows whether a vehicle fits; ParkingLot coordinates; PricingPolicy owns the fee rule.

The van (155 minutes → 3 started hours) pays 200 + 2×150 = 500 cents.

Alternative flows from the use case appear explicitly as std::optional results.

Common Mistakes

Noun-only class discovery — every noun becomes a class (Barrier, Driver, Car Park Building) while the real rules (pricing, allocation) are hidden in a god class.

Big up-front design — months of diagrams before any code; iterate in small slices instead.

Designing the database first and calling the tables "the domain model".

Ignoring alternative flows — most bugs live there.

Trade-offs and Alternatives

Lightweight OOAD (an hour of CRC cards and two sequence diagrams) pays off for any non-trivial feature; heavyweight modelling rarely does. Event storming and example mapping are collaborative alternatives for discovering domain events and rules.

Real-World Use

Interview "design a parking lot / elevator / library" questions are OOAD exercises. In industry, OOAD happens in design reviews, RFCs and story-mapping sessions, and its results live as diagrams, tests and code.

Practice

Q1. (Easy) What do the three letters of CRC stand for?

Answer: Class, Responsibilities, Collaborators.

Q2. (Medium) Why did PricingPolicy become a class although the driver never mentions it?

Answer: It encapsulates a rule that changes independently (rates, discounts); giving it its own class isolates that change.

Q3. (Advanced) Add the "oversized vehicle" alternative flow: vehicles larger than any spot must be rejected with a clear reason. Sketch the change.

Answer: Return std::expected<Ticket, ParkError> with ParkError::NoSpotFree and ParkError::VehicleTooLarge; ParkingLot checks the largest spot size first (or the lot exposes maxSize()), so the driver gets a different message for "come back later" and "you can never park here". Add a test for each error.

Interview Question

Q1. "How do you find the classes for a new system?" Answer: Start from use cases and domain rules rather than nouns alone: identify responsibilities (what must be known/decided/done), group them by cohesion and by the information they need, and give each group a class; use CRC cards to check collaborations; walk key scenarios with sequence diagrams to see whether responsibilities are in the right place; extract classes for rules that change (policies) and value objects for concepts with validation. Iterate with tests.

Quick Quiz

Q1. Which principle assigns a responsibility to the class that has the needed data?

Answer: Information Expert (GRASP).

Q2. What should alternative flows in use cases become in the design?

Answer: Explicit code paths and tests.

Q3. Is every noun a class?

Answer: No — some nouns are attributes, actors or external systems, and some classes (policies) aren't nouns in the requirements.

Summary

OOAD: use cases → domain model → CRC → interactions → states → interfaces → code, iteratively.

Find classes by responsibilities and rules; trace requirements to code and tests.

Related: Modules 23–24, 74 (DDD), 83–89 (projects and interview design questions).