Composition: “Has-a” Relationships
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.
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)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}]"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))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.