Skip to content
myAudience.
← Explore the library

Objects, encapsulation and defensible design

By UK A Level Computer Science · independent study · myAudience

Wallet balance changes from 700 to 450 pence after spending 250; spending 500 is rejected and leaves 450 unchanged.

Original textbook concept plate generated with OpenAI imagegen; labels and states checked against the lesson model. · Generated original teaching artwork; pending operator rights/provenance review before publication.

Objects, encapsulation and defensible design

A class defines a family of objects with state and behaviour; an object is an instance created from that class. Instantiation creates an object, often through a constructor. Encapsulation associates the state with operations that preserve its rules, and information hiding prevents callers from depending on unnecessary implementation detail. The original Wallet class stores integer pence and exposes spend and balance operations. Its invariant is that the stored balance is a nonnegative integer after construction and after each successful method call.

The constructor rejects an invalid initial balance. spend rejects a negative amount and any amount greater than the current balance before changing state. This ordering makes a rejected operation leave the balance unchanged. Using pence avoids floating-point approximations of decimal currency in this simplified model. It does not solve every financial problem: exchange rates, fractional charges, concurrent purchases and persistent audit records require additional contracts. Keeping a small model precise is better than treating a classroom object as a complete payment system.

Python's leading underscore is a convention indicating a non-public implementation detail; it does not prohibit external access. A caller can still assign wallet._pence=-5 and violate the invariant. Our correctness argument therefore assumes callers use the public methods. Languages with enforced private access have different controls. Encapsulation is more than writing an underscore: it requires a coherent interface, a stated invariant and clients that respect that boundary. The class in this lesson is intentionally not claimed to cover all object-oriented techniques.

Inheritance expresses an is-a relationship when a derived type can satisfy the base type's behavioural promises. Overriding supplies a derived implementation of a method; polymorphism lets the same operation call select behaviour appropriate to the actual object. A subclass that lets spend make a balance negative would violate Wallet's current invariant and would not be a safe substitute under this contract. Changing a class name does not repair the behavioural mismatch. If overdrafts are required, redesign the promised invariant and the client assumptions explicitly.

Composition models a part owned within a larger object; aggregation models a looser association whose participant can exist independently. A wallet might compose an internal transaction list while a club aggregates existing member objects. UML convention uses a filled diamond for composition and an open diamond for aggregation, placed at the whole end. These relationships are not equivalent to inheritance. Prefer a composed policy object when spending rules vary independently of wallet identity, but recognise that “favour composition” is a design guideline rather than a theorem that inheritance is always wrong.

Reading the diagram: Read the three panels as one object over time: constructing Wallet(700) establishes 700 pence, spend(250) leaves 450, and attempting spend(500) rejects before subtraction. The final balance remains 450; the rejection symbol is not a successful payment. The wallet drawing stands for encapsulated state, not a real payment service. The invariant is guaranteed only when callers respect the methods; an underscore does not enforce privacy in Python.

Runnable original Python 3, independently executed on Python the related topic. Read the stated input contract before running. These examples use fictional data and are teaching models, not production services.

```python
from dataclasses import dataclass

from dataclasses import dataclass

@dataclass
class Wallet:
"""Store integer pence and preserve nonnegative balance on every method."""
_pence: int

def __post_init__(self):
if type(self._pence) is not int or self._pence < 0:
raise ValueError('nonnegative integer balance required')

def spend(self, amount):
if type(amount) is not int or amount < 0 or amount > self._pence:
raise ValueError('invalid spend')
self._pence -= amount

def balance(self):
return self._pence
```

Worked example: Create Wallet(700), spend 250, then attempt to spend 500. Explain state after each call.

Construction establishes an integer balance of 700 pence.

The first spend is valid, so the new balance is 700−250=450 pence.

500>450, so the second spend raises ValueError before subtraction and leaves the balance at 450.

Answer: Balance=450 pence. Rejected spending does not mutate the state.

Check your understanding: 1. Distinguish class from object. 2. Why reject spend(-100)? 3. A subclass silently grants unlimited overdrafts. Evaluate substitutability and propose a composition-based alternative for a new account contract.

Answer: 1. Wallet is the class; Wallet(700) creates an object. 2. Subtracting a negative amount would increase the balance and violate the meaning of spending. 3. Unlimited overdrafts break the base nonnegative invariant. Define a new account abstraction whose contract explicitly allows a credit limit and compose a spending-policy object that checks the allowed lower balance. Existing Wallet clients must not receive it under the old promise.

Why: Polymorphism is dispatch through a common interface, not merely having several classes. Private naming conventions do not enforce isolation. A derived method must preserve the behavioural contract relied on by callers. UML diamonds distinguish ownership relationships; they do not indicate execution order.

Author source references