0%

Inheritance and Polymorphism · capstone

Capstone project: Project: Support Several Question Types

The course opened with a quiz whose questions were , all the same shape, checked by one that knew the rule. It ends this chapter with a quiz holding four kinds of question, scored by code that knows none of them.

What you are building

Question          the base: prompt, points, optional collaborators
  TextQuestion    matches ignoring spaces and case
  ChoiceQuestion  matches one option
  NumericQuestion matches within a tolerance
  SurveyQuestion  ungraded, always correct

TimeLimit         a thing a question has
Hint              a thing a question has

Quiz              holds questions of any kind
ScoreReport       turns an attempt into lines

Four subclasses because each is a genuinely different kind, one level deep. Two collaborators because timing and hints combine freely with all four.

The promise every subclass keeps

is_correct(response) takes any and returns True or False. It never raises, and it never returns anything else.

That sentence is the reason Quiz and ScoreReport can be written once. The grader checks it against every subclass with a blank response and a nonsense one, which is the cheap version of the family test Chapter 13 builds properly.

SurveyQuestion is ungraded and always correct. Why is it a subclass rather than a separate unrelated class?

Rules the grader checks

  • No function outside the classes uses isinstance or names a subclass.

  • Every subclass returns a from is_correct, for any string.

  • describe() is written once, on the base.

  • No class prints.

  • A fifth kind of question, invented by the grader, works everywhere without a change.

That last one is the real test of the chapter. If a class written after your code, by someone who never saw it, scores and reports correctly, then the design does what it claimed.

Finishing

Read your finished Quiz and ScoreReport and count the times they mention a specific kind of question. The answer should be zero.

Then invent a fifth kind. A true-or-false question, a keyword question that accepts several spellings, or a question all work. Add it to the quiz without editing Quiz or ScoreReport. Seeing that new behavior flow through the unchanged program is the project’s finish line.

Task

The validated collaborators and base class are supplied. Build the concrete family and the two classes that use it without knowing concrete .

Question(prompt, points, timing=None, hint=None) holds the shared state. describe() returns "<prompt> (<points> points)" followed by each collaborator’s description that is present, timing before hint. is_correct(response) raises NotImplementedError. Add a kind_label property returning "question".

The four subclasses each override is_correct and kind_label, and none of them redefines describe:

  • TextQuestion(prompt, points, answer, ...), label "text", matching ignoring spaces and case;

  • ChoiceQuestion(prompt, points, correct_option, ...), label "multiple choice", matching the option in either case;

  • NumericQuestion(prompt, points, answer, tolerance, ...), label "numeric", within tolerance, and returning False rather than raising when the response is not a number;

  • SurveyQuestion(prompt, ...), label "survey", worth 0, always correct.

TimeLimit(seconds) and Hint(text) are supplied and validated. Keep the earlier constructor rules too: text answers and choice options must not be blank, numeric tolerance must be non-negative, and a quiz title must be non-empty. Reject these invalid values with .

Quiz(title) holds questions, hands them out safely, and reports question_count and total_points. ScoreReport(quiz, responses) returns lines(): one "<description>: correct" or ": incorrect" per question, then "Score: <earned> of <total>", then "Kinds: <labels, comma separated, in order, without repeats>".

Quiz.add(None) raises ValueError. A report must receive exactly one response per quiz question; lines() raises ValueError on either too few or too many so zip cannot silently discard data.

Every is_correct must return True or False for any , never raising. No class prints, and nothing outside the family names a subclass.