A class can represent a concept and the operations allowed on it. The useful question is not how many classes a project contains, but whether each one makes a rule clearer and easier to test.
Model a meaningful responsibility
An object combines state with behaviour. In a small counter, the state is the count and the behaviour is adding a valid amount. Give the class a purpose that can be explained in one sentence. Creating a class for every noun in a requirement often produces extra indirection without improving the design.
Protect the rules at the boundary
Validate an operation before changing state. If an amount must be non-negative, reject an invalid amount before adding it. This example demonstrates one local rule; a real application also needs to decide where persistence, concurrency and access checks belong. Encapsulation is about maintaining a reliable interface, not simply hiding every attribute.
Prefer a design you can replace and test
Use composition when one object can collaborate with another through a small interface. Inheritance is useful for some relationships but can also create tight coupling. A test should focus on the promised behaviour, such as the resulting count or rejected input, rather than the exact private arrangement of fields.
Worked example
The class starts at zero and accepts an addition of three. A negative value raises an error before state changes. The example deliberately avoids a database so the object rule can be understood independently.
class Counter:
def __init__(self):
self.value = 0
def add(self, amount):
if amount < 0:
raise ValueError("amount must be non-negative")
self.value += amount
counter = Counter()
counter.add(3)
print(counter.value)
Expected output
3Check your understanding
Try each question before opening its explanation. These are free practice prompts; they do not use game chances.
Is a class always better than a function?
No. A short stateless calculation may be clearer as a function. A class becomes useful when a coherent concept has state, rules or collaborators that benefit from a shared interface.
What does composition mean here?
It means building behaviour by giving an object another object to work with, instead of inheriting all behaviour from a parent. For example, a report writer can receive a storage adapter.
What should the count be after a rejected negative addition?
It should remain at its previous valid value. Rejecting the input before mutation makes this rule straightforward to test and explain.
Sources and further reading
These original examples explain foundational concepts. Use the official references for complete, current details and version-specific behaviour.
About this guide: AI-assisted educational content with original examples and linked primary references. Found an issue? Tell us what to improve.