2.2.1 - Designing and implementing object-oriented solutions
An object-oriented design becomes useful when its classes can be constructed, called and traced correctly. You will turn a small scenario and class diagram into attributes and instance methods, protect state through an interface, and implement inherited behaviour with overriding. Python and OCR pseudocode use different syntax for these same design decisions.
Derive a class interface from state and responsibilities
A class defines the state and behaviour its objects will have. Start from a scenario's responsibilities, rather than mechanically making every noun a class. A visitor pass has an owner and can produce a display label. A timed pass is a specialised pass with a duration; a guest pass is another specialisation with different label behaviour.
| Class | Per-object state | Required behaviour |
|---|---|---|
| Pass | owner | Initialise owner; return a label |
| TimedPass | inherited owner, plus hours | Initialise both; return a timed label |
| GuestPass | inherited owner | Return a guest label |
Attributes hold object state; methods implement behaviour. The label is computed from state, so it need not be stored as a second copy that could become inconsistent. Inheritance expresses an “is a” relationship: every TimedPass is a Pass. A pass which merely uses a printer does not become a Printer subclass; using an object is different from being its specialisation.
[DIAGRAM: One Pass superclass with TimedPass and GuestPass subclasses; asset_slug: pass-inheritance; caption: Subclasses inherit Pass and provide their own label method.]

The hollow arrowheads point to the superclass. In the diagram, - indicates a private attribute and + a public method. The repeated label() identifies an override of the same operation. The diagram is a partial interface: constructor signatures are added in the implementation below. Its private owner is accessed through superclass behaviour rather than directly from outside.
To implement a diagram, read each name, visibility, parameter/result contract and relationship. An object is an instance, not another class definition. Two passes may share a class while holding different owner values. All methods used here are instance methods, called on an object; OCR's appendix does not require static methods.
Constructors establish each object's initial state
A constructor receives the information needed to initialise a new object's state. OCR names its constructor procedure new, creates an instance with new ClassName(arguments), and uses public and private where visibility matters.
class Pass
private owner
public procedure new(givenOwner)
owner = givenOwner
endprocedure
public function label()
return owner
endfunction
endclass
first = new Pass("Amira")
second = new Pass("Bo")
print(first.label())
print(second.label())
The outputs are Amira then Bo. The constructor parameter givenOwner supplies initial data; owner belongs to that particular object. Calling first.label() uses first's state. Method definitions describe behaviour once; each instance carries its own attributes.
Python calls a class to create an instance and automatically invokes __init__ to initialise it. self names the current instance inside its method; Python supplies it when the method is called on an object.
Predict the two labels before running this ungraded workspace. Each owner is a string. Change constructor arguments to explore separate instance data, then change the method to explore behaviour shared through the class definition.
Create independent pass objects
{"exercise_id":"h446-oop-instance-sandbox","task_version":"1","runtime_profile":"python-console-v1","original_filename":"main.py","file_mode":"fresh","code":"class Pass:\n def init(self, given_owner):\n self.__owner = given_owner\n\n def label(self):\n return self.__owner\n\nfirst = Pass("Amira")\nsecond = Pass("Bo")\nprint(first.label())\nprint(second.label())\n","instructions":"Predict, then Run. Change only the second owner to Eli: predict whether the first label changes. Try an empty string as the second owner to observe the boundary label. Restore Bo, then change label to return "Pass:" + self.__owner and predict both results. Restore the method and try Pass() for the first construction to observe the missing-argument TypeError, then restore its argument. These runs are ungraded and do not answer the later scored tasks.","files":[]}
The caller supplies one argument to Pass, though __init__ has self plus that explicit parameter. __init__ initialises the already-created instance; it should not return an ordinary result. A returning method such as label is different: its result can be printed or stored by the caller. Do not translate OCR new into an ordinary Python method named new and expect automatic initialisation.
Here double underscores mark owner as intended private state and trigger Python name mangling. This helps avoid accidental name clashes and direct access, but is not a security barrier or enforced privacy. Python does not have OCR's private keyword. OCR's 2023 scheme accepts Python private attributes indicated by double underscores or explicit private comments; meaning and the stated access contract matter, not identical model syntax.
Encapsulation makes state changes obey a contract
A well-designed object exposes operations which preserve its state rules.
Encapsulation
Encapsulation groups state with its operations and exposes an interface which controls access to that state.
A private attribute can be read through a getter and changed through a method which checks the requested operation. Privacy alone does not guarantee that the method preserves a sensible state; its logic must enforce the required rule.
A locker initially has one token. take should remove a token only when one is available and return whether it succeeded:
class Locker:
def __init__(self, tokens):
self.__tokens = tokens
def get_tokens(self):
return self.__tokens
def take(self):
if self.__tokens > 0:
self.__tokens -= 1
return True
return False
locker = Locker(1)
print(locker.take())
print(locker.take())
print(locker.get_tokens())
The outputs are True, False and 0. The second request is rejected without making the count negative. A caller uses locker.take() rather than reaching directly into the private state. If storage later changes, callers can continue using the same public method contract. The constructor here assumes a non-negative integer; it does not validate arbitrary inputs. State that assumption rather than claiming complete validation.
In OCR pseudocode, the corresponding interface uses private tokens, public function getTokens() returning tokens, and a public take function which checks tokens before decrementing. A private member is accessed within its class, whereas external code uses the public methods. Writing locker.tokens = -2 would violate that interface even if a different language happens to permit public field access. The distinction is about a deliberate design contract.
For a different ungraded case, a battery may use a requested number of units only when that number is positive and no greater than its remaining charge. Initial charge is a non-negative integer and requested units are integers. The method returns whether the request succeeded; the getter reveals the resulting state.
Continue this lesson.
You’re previewing NovaLearn without an account. Create a free account to read the rest.
Create free account