Skip to content
All articles

Object-oriented software design: responsibilities, interfaces and change

Application source code photographed on a monitor
Photo by Pixabay on Pexels

Design object-oriented software around responsibilities that someone can explain and changes the application must accommodate. Use classes to express those decisions, then inspect the interfaces and state transitions with a small worked example. A large class hierarchy is not an objective in itself; the useful result is a clear route from a request to its outcome.

Which responsibility belongs to which object?

Start with the vocabulary of the task. In a hypothetical equipment-inspection application, a recorded inspection, an inspection rule and a notification destination might need different responsibilities. Ask what each one knows, what it may change and which result it must produce. Keep those answers in ordinary language before choosing class names.

Python describes classes as a way to bundle data and functionality. Creating a Python class creates a new object type from which instances can be made. The Python classes tutorial supplies concrete language mechanisms for this discussion. A design decision still needs a reason related to the application.

For the hypothetical inspection, propose that an Inspection record owns the entered observations, a Rule evaluates an agreed condition and a Notification destination receives an approved result. This is a candidate responsibility map, not a finished implementation. Check whether the actual workflow calls for those distinctions before treating them as requirements.

Ask where an unclear responsibility would be resolved. If both the record and the notification destination decide whether an inspection passes, write down the duplication and decide which rule should be authoritative. Avoid settling that question merely by creating another shared helper with an ambiguous name.

What state changes need explicit rules?

Python class instances can hold attributes that maintain their state. Python instance methods can modify the instance state. Use those capabilities deliberately: identify the allowed state change, the information required to request it and the response when the request is inappropriate.

For example, the proposed inspection design might distinguish an editable draft from a submitted observation. Ask the business owner whether submission is reversible and who may request a correction. Record those answers as application decisions. The Python mechanism does not establish the organisation’s policy or decide whether a particular user is authorised.

Keep an example of a rejected change beside the ordinary path. If the hypothetical record has already been submitted, what should a second submission mean? Ask for a precise outcome rather than accepting an undefined “error”. A developer can then describe the intended return value, exception or other contract in the language of the chosen implementation.

Also identify what the object does not own. A rule that evaluates a condition need not silently send an external message as part of the same conceptual responsibility. Discuss that proposed boundary with the team, especially where the application needs to explain an outcome after an external service is unavailable.

When is an interface useful?

Python’s abc module supplies infrastructure for abstract base classes. The ABC helper lets a class define an abstract base through inheritance. The abc module documentation gives Python teams one mechanism for expressing an interface-like requirement through abstract members.

An ABCMeta-based class with abstract members cannot be instantiated until those members are overridden. A design review should still ask whether the implemented method honours the intended behaviour. Supplying a method with the required name does not settle what it does with a missing observation, a rejected inspection or an unavailable notification destination.

Propose a small contract card for the hypothetical notification destination: required input, successful acknowledgement, unresolved outcome and permitted side effects. Ask two candidate implementations to explain those cases using the same vocabulary. This is a useful comparison exercise whether or not the team ultimately chooses an abstract base class.

Avoid using an interface as a promise of unlimited interchangeability. Record the assumptions the caller depends on. If one candidate stores a result locally and another contacts an external service, ask how the caller is meant to distinguish “recorded”, “sent” and “outcome unknown”. These are questions to resolve for the design, not behaviours assumed of either candidate.

How should inheritance be assessed?

Python allows derived classes to override methods inherited from base classes. Treat an override as a point to examine: does the specialised behaviour still make sense to the code that uses the base contract? The proposed inspection exercise should include the outcomes that matter to the caller, rather than checking only that a subclass can be constructed.

Registering a virtual subclass does not place the registering ABC in its method resolution order. Do not use virtual registration as evidence that methods have appeared on an unrelated class. Ask the developer to demonstrate the intended interface against the actual candidate object in a controlled exercise.

Python documents that abstract methods can contain an implementation callable through super. For a shared implementation, record which behaviour is inherited, which is replaced and which invariants the specialised class must preserve. That explanation is more useful than selecting inheritance because two class names happen to sound similar.

Use this original change-impact matrix to keep the discussion tied to concrete decisions. Fill it in for one proposed change before adding new abstraction.

Proposed change Responsibility to inspect Evidence the reviewer needs
Add a new inspection rule Evaluation input and result contract Ordinary and rejected-condition examples
Change the destination of notifications Boundary between result recording and external delivery Acknowledged, failed and unresolved outcome definitions
Allow a correction after submission State ownership and permitted transition Business decision and a correction example
Introduce a specialised inspection type Base contract and overridden behaviour Cases that callers expect every type to handle
Replace an implementation Assumptions at the interface boundary Equivalent task outcomes and documented differences

The matrix does not mandate a particular design pattern. Its purpose is to reveal which behaviour must stay coherent when a change arrives. If a proposed abstraction adds no explanatory value for the named change, ask what simpler design would remain understandable.

How can reviewers use the design record?

Walk the hypothetical inspection through the responsibility map from creation to a recorded outcome. At each boundary, name the information passed and the decision made. If the discussion jumps from an entered observation straight to a claim that a notification was delivered, return to the contract card and resolve the missing acknowledgement semantics.

Keep code review and business-rule review connected. The developer should point to the relevant mechanism; the workflow owner should recognise the intended outcome. A diagram becomes useful when those explanations agree and the unresolved cases remain visible.

For a wider application brief, see web development and web applications versus websites. Discuss your requirements with Spout using the responsibility map and one change you need the design to accommodate.

By Spout Team. AI-assisted drafting by Codex using the linked primary documentation.

Make your next move.

A website, a store or a clearer digital plan. Let’s start with your business.

Start your brief