Inheritance and Polymorphism · capstone
Capstone project: Project: Support Several Question Types
The course opened with a quiz whose questions were
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 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
isinstanceor 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 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 returningFalserather than raising when the response is not a number;SurveyQuestion(prompt, ...), label"survey", worth0, 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