0%

Chapter 6 · practice

Objects Working Together

Pass One Object to Another

One line from the last chapter is worth looking at again:

attempt.record(question, response)

An Attempt receives a whole Question. It could have been written to receive the pieces instead:

attempt.record(question.prompt, question.answer, question.points, response)

Both work. The difference between them is the subject of this chapter, and it decides how much of your program has to change when something small changes.

Taking an object apart at the door

Here is a report_line written the second way:

Try it

Read the call. Every caller has to know that a question has exactly these three parts, in this order, and has to pull them out by hand. Then look at the body: correct = response == answer recreates the rule that Question.is_correct already owns.

Now suppose questions gain a case_sensitive option. This function’s signature changes, every call site changes, and the comparison rule has to be updated here as well as in Question. The function never asked for that job.

Passing the whole thing

Try it

Same output. The call is now report_line(question, "3"), which reads like what it means, and the comparison rule is asked for rather than reproduced. Add case_sensitive to Question now and this function needs no change at all, because it never knew how correctness was decided.

A question gains a new attribute, and is_correct starts using it. Which version of report_line has to change?

The signal to watch for

There is a name for the smell in the first version: a long whose all come from the same , followed by code that reassembles a decision that object could have made.

You can spot it at the call site, without reading the function at all:

summarize(attempt.learner, attempt.score, attempt.response_count)

Three arguments, one source. That call is quietly asking for an Attempt.

When the pieces are the right answer

Do not turn this into a rule that functions must always receive objects. A function that genuinely works on one should receive that value:

def shorten(text, limit):
    ...

shorten has no business receiving a Question. It works on text, and passing it a question would make it useless for every other in the program. The question to ask is which values the function’s job actually depends on. When they all come from one object, and it needs to make decisions that object could make for itself, pass the object.

The exercise has a function with the first shape. Give it the second.

Task

Both here take an apart at the door and then rebuild decisions the object could make itself.

Rewrite describe_result to take (question, response), and summarize_attempt to take (attempt). Ask each object for what you need instead of receiving its parts, and use the it already provides rather than repeating their rules.

Update the two calls at the bottom to match. The printed output must not change: the point of the exercise is that the interface improved while the behavior stayed put.