Loose coupling is a relationship between software components in which each has few dependencies on the implementation details of the others. Minimizing these dependencies makes change less likely to spread through the system. section 4.2.2
A class becomes dependent when it references another class. Prefer passing objects as arguments, aggregating collaborators directly, or creating them locally; avoid reaching through one object to call methods on another object returned by it. section 5.6
Encapsulate changing book attributes
A catalogue initially knows individual book attributes for adding and searching books. Adding publication year and genre then forces changes in both Catalogue methods. Encapsulate those varying attributes and their is_match() behavior in Attributes. Catalogue passes Attributes objects and delegates matching, so later attribute changes remain localized. section 2.3.2
Design choices that reduce implementation knowledge include:
- Make collaborators depend on stable operations rather than their storage or internal representation. section 4.2.2
- Replace collection-specific reporting logic with a common iteration abstraction when a report must support lists, generators, and dictionaries. section 11.1.2
- Register interchangeable subscribers instead of hardcoding each subscriber’s method name and implementation. section 12.1.2
- Represent state-specific behavior in State subclasses and let the current state handle actions and return the next state. section 13.1.3
- Choose composition with Strategy when complete algorithms must be interchangeable; choose inheritance with Template Method when a superclass owns the algorithm outline. section 8.3
Listing 2.4 (Program 2.2 Books-2): attributes.py
from enum import Enum
class Genre(Enum): #1
UNSPECIFIED = 0
ADVENTURE = 1
CLASSICS = 2
DETECTIVE = 3
FANTASY = 4
HISTORIC = 5
HORROR = 6
ROMANCE = 7
SCIFI = 8
def __str__(self): return self.name.lower()
class Attributes:
@staticmethod
def _equal_ignore_case(target_str, other_str): #2
if len(target_str) == 0:
return True
else:
return target_str.casefold() == other_str.casefold()
def __init__(self, title, last, first, year, genre):
self._title = title
self._last = last
self._first = first
self._year = year #3
self._genre = genre #3
@property
def title(self): return self._title
@property
def last(self): return self._last
@property
def first(self): return self._first
@property
def year(self): return self._year
@property
def genre(self): return self._genre
def is_match(self, target_attrs): #4
return (
Attributes._equal_ignore_case(target_attrs.title,
self._title)
and Attributes._equal_ignore_case(target_attrs.last,
self._last)
and Attributes._equal_ignore_case(target_attrs.first,
self._first)
and ( (target_attrs.year == 0)
or (target_attrs.year == self._year))
and ( (target_attrs.genre == Genre.UNSPECIFIED)
or (target_attrs.genre == self._genre))
)
def __str__(self):
return (f"{{TITLE: '{self._title}', LAST: '{self._last}', "
f"FIRST: '{self._first}', YEAR: {self._year}, " #5
f"GENRE: {self._genre}}}") #5
Inspect the Attributes class that encapsulates book fields, case-insensitive matching, and genre enumeration. section 2.3.2
Figure 13.3 The subclasses of superclass State encapsulate handling the customer and machine actions for each state of the ticket machine. Each action method of the subclasses returns a State object that is the next state for the ticket machine to transition to. By aggregating the State subclasses in class StatesBlock, the subclasses are loosely coupled with each other and with class TicketMachine. Each action method of class TicketMachine defers to the corresponding method of the current State object pointed to by its private _state instance variable.
Trace how State subclasses isolate ticket-machine behavior and how StatesBlock connects them without making TicketMachine manage every transition. section 13.1.3
















