Choosing the Right Abstraction
Learning Objectives
- You can choose among functions, records, and classes to suit the work a program needs to do.
- You can distinguish stored state from results calculated from it.
- You can critique an AI-proposed program structure using its requirements and how easily it can be changed.
- You can avoid introducing structure that adds work without clarifying the program.
Functions, dictionaries, and classes offer different ways to organize a program. Choose the combination that makes its responsibilities easy to understand and change.
An abstraction lets a caller use an operation without knowing its implementation details. For example, borrow_item("E10") expresses the borrowing request while hiding the search through the item collection. A useful abstraction gives the caller less to keep track of.
Sometimes a Function Is Enough
The function below calculates a total without keeping state between calls. The class stores a reference and uses the same calculation. Predict the four results before running it.
Both produce 50 before the append and 60 afterward. Storing sessions stores a reference: changes to that list remain visible through self.sessions. A class wrapper does not make data independent or validated automatically.
The wrapper might fit a larger design, but for one total it adds object creation and stored state without answering an additional requirement. The function is a clear default for the stated task.
Sometimes a Plain Record Is Enough
A CSV import row may need only named fields and validation. A dictionary can hold these named values, and csv.DictReader already returns that representation. This row does not need methods that maintain state across operations.
A LoanItem instance has operations and an ongoing availability state. A class gives those operations a shared home and lets callers ask the instance to perform them.
The same application may use both: dictionaries when reading or writing files and class instances when handling borrowing requests. Conversion functions connect these representations. Dictionaries are objects too; the choice here is between a dictionary record and an instance of a class we defined.
Avoid Storing the Same Fact Twice Without a Reason
A game history can already determine its total score:
def total_points(history): total = 0 for record in history: total = total + record["points"] return totalIf an object also stores a separate total, every operation changing history must keep that total synchronized. This may be justified in a different setting, but it creates another rule to maintain.
For this history example, calculating the total is simple. Resetting or restoring history then cannot accidentally leave an old cached total behind.
The broader question is:
Which values must we store, and which can we calculate from them?
Keep Public Methods Focused
A class’s interface consists of the operations that callers are expected to use, such as its public methods. This is different from a user interface, which provides the prompts, controls, and messages a person uses.
A caller of borrow_item(item_id) should not need to know how the library stores its items. Adding methods solely to expose every internal detail can undermine that boundary.
Adding several methods that only call one another can make simple behavior harder to follow. A helpful name should remove a detail the caller does not need or express a meaningful operation.
Part 3 made the same point about functions: do not turn every few lines into a new function. Classes deserve the same restraint.
Review a Proposed Program Structure
An AI assistant can suggest plausible names without establishing what each component needs to do. Review a proposal by tracing one input through reading, conversion, validation, and writing. Ask what information each step needs and whether any state must remain between operations.
First, compare choices for concrete requirements. Then justify a design and identify where a small requirement change belongs.
Use those distinctions to critique the proposed program structure in your own words.
Review an importer design
0 / 10 points
A program reads one CSV catalogue, validates its records, and saves them as JSON. Each item has an identifier, name, category, and availability. The three text fields must be nonempty after trimming, and identifiers must be unique. Invalid records are rejected. CSV availability is written as yes or no; the JSON records use boolean True or False values in Python before they are encoded.
An assistant proposes the classes FileName, TextField, ItemIdentifier, CsvReaderManager, RecordConversionManager, AvailabilityDecisionManager, and CatalogueResultPrinter.
Propose a design for the importer. Explain which responsibilities you would keep together and whether any class needs state that persists between operations. Then consider a change to CSV availability spellings: available and on-loan replace yes and no, while the saved meaning stays the same. Explain where that change belongs and give a concrete test, with an expected result, that checks the meaning is preserved.
Compare the importer’s work with an interactive equipment library that keeps items between commands and lets users borrow and return them. There is no required class count; justify your choices with the work each component does.
Check Your Understanding
- Why might a state-free calculation be clearer as a function?
- Why can dictionaries and classes both be useful in one application?
- What extra obligation appears when both history and its total are stored?
- What evidence would justify retaining a proposed class?
- Why is the number of classes a poor measure of design quality?