Python Programming from First Principles: Learn to Code, Think Computationally, and Solve Real-World Problems
PDFEPUB
BOOK 000 · CHAPTER 7 · Composition: “Has-a” Relationships

Composition: “Has-a” Relationships

On this pageDefine the ScheduleGive Each Course a ScheduleUse the Collaborating Objects

Composition builds an object from other objects. A Course has a Schedule and delegates meeting-related work to it.

The UML association arrow points from Course to its Schedule. The 1 means each course refers to one schedule; 0..* allows a schedule to be shared by several courses, which this constructor permits. A filled composition diamond would additionally require exclusive ownership.

Course+schedule+add_meeting(day, time_range)+conflicts_with(other_course)+summary()Schedule+add_meeting(day, time_range)+conflicts_with(other)schedule10..*

Define the Schedule

any(...) checks whether at least one generated condition is true. __str__ supplies the display text used when a schedule is formatted.

class Schedule:
    """Holds weekly meeting times like ('Mon', '09:00-10:30')."""
    def __init__(self):
        self._slots = []  # list of (day, time_range) tuples

    def add_meeting(self, day, time_range):
        entry = (day, time_range)
        if entry not in self._slots:
            self._slots.append(entry)

    def conflicts_with(self, other):
        return any(slot in other._slots for slot in self._slots)

    def __str__(self):
        return "no meetings scheduled" if not self._slots else ", ".join(f"{d} {t}" for d, t in self._slots)
Listing 307 — Define the Schedule

Defines Schedule; no visible output.

add_meeting stores a unique (day, time_range) tuple; conflicts_with tests those exact pairs.

Give Each Course a Schedule

Continue with the Schedule definition:

class Course:
    """A course that has a schedule."""
    def __init__(self, code, title, credits, schedule=None):
        self.code = code
        self.title = title
        self.credits = int(credits)
        self.schedule = schedule or Schedule()  # composition

    def add_meeting(self, day, time_range):
        self.schedule.add_meeting(day, time_range)

    def conflicts_with(self, other_course):
        return self.schedule.conflicts_with(other_course.schedule)

    def summary(self):
        return f"{self.code} — {self.title} ({self.credits} cr) [{self.schedule}]"
Listing 308 — Give Each Course a Schedule

Defines Course; no visible output.

The course creates a Schedule when none is supplied and forwards calls to it.

Use the Collaborating Objects

cs101 = Course("CS101", "Intro to CS", 6)
cs101.add_meeting("Mon", "09:00-10:30")
cs101.add_meeting("Wed", "09:00-10:30")

dh200 = Course("DH200", "Methods in DH", 4)
dh200.add_meeting("Wed", "09:00-10:30")  # overlaps with CS101

his110 = Course("HIS110", "World History", 3)
his110.add_meeting("Tue", "14:00-15:30")

print(cs101.summary())
print(dh200.summary())
print(his110.summary())

print("CS101 vs DH200 conflict?", cs101.conflicts_with(dh200))
print("CS101 vs HIS110 conflict?", cs101.conflicts_with(his110))
Listing 309 — Use the Collaborating Objects

Output

CS101 — Intro to CS (6 cr) [Mon 09:00-10:30, Wed 09:00-10:30]
DH200 — Methods in DH (4 cr) [Wed 09:00-10:30]
HIS110 — World History (3 cr) [Tue 14:00-15:30]
CS101 vs DH200 conflict? True
CS101 vs HIS110 conflict? False

CS101 and DH200 contain the same Wednesday slot; HIS110 shares none. This checks identical slots, not overlapping time intervals such as 09:00–10:30 and 10:00–11:00.

Try it: add the same meeting twice and print the summary. Then add a different slot and explain the result.

A reference to another object does not by itself enforce exclusive ownership. The UML sections will distinguish association from composite ownership. Composition combines objects; inheritance, introduced next, defines a specialized type from an existing class.