Object-Oriented Programming~20 min

Interfaces and Public Behavior

Distinguish what other code is meant to rely on from what's just an implementation detail — so internals can change without anything breaking.

By the end of this lesson, you can

  • Distinguish an object's public interface from its internal implementation details
  • Explain why code that only depends on an object's interface keeps working when its internals change
  • Identify which parts of a class are meant to be used from outside it

Why it matters

LibraryBook currently tracks loan status with a boolean, is_on_loan. What if the library later wants to know when a book is due back, and switches to storing a due date instead? Any code that called book.borrow() shouldn’t need to change at all — but code that directly read book.is_on_loan would break. The difference between those two outcomes is exactly what an interface is for.

Mental model

An object’s interface is the part meant to be relied on from outside it — usually its methods. Everything else is an implementation detail: how the object achieves what its methods promise, which is free to change as long as the promise itself doesn’t.

  • Public interface: book.borrow() — a promise about behavior (“attempting to borrow either succeeds or it doesn’t”), independent of how loan status happens to be stored internally.
  • Implementation detail: exactly how is_on_loan is represented — a boolean today, possibly a due date tomorrow.

Code that only calls borrow() never needs to know or care which one it is.

Small example

PythonThe interface stays the same; the internals could change
class LibraryBook:
    def __init__(self, title):
        self.title = title
        self.is_on_loan = False

    def borrow(self):
        if self.is_on_loan:
            return False
        self.is_on_loan = True
        return True


book = LibraryBook("Dune")
print(book.borrow())
Output
True

Nothing about calling book.borrow() reveals — or needs to reveal — that is_on_loan is a boolean. If a future version tracked a due date instead and had borrow() check “is due_date None?”, every call to book.borrow() would keep behaving exactly the same from the outside.

Classify the members

Sorting LibraryBook's members into interface vs. implementation detail
MemberPublic interface, or internal detail?Why
borrow()Public interfaceDescribes behavior other code is meant to rely on
titlePublic interface (read-only fact)A stable fact about the book, safe to read directly
is_on_loanInternal detailHow loan status happens to be represented — could change

Check your understanding

A future version of LibraryBook renames is_on_loan to on_loan. Which code breaks?
Or reveal the answer without checking

Answer:code elsewhere that directly reads book.is_on_loan
borrow() can be updated internally to use the new attribute name without changing what it promises to callers. Code outside the class that reached directly into is_on_loan has no such protection — it breaks the moment that name changes.

What makes something part of an object's interface, in the design sense used here?
Or reveal the answer without checking

Answer:Whether it's a promise about behavior that other code is meant to rely on, regardless of how it's implemented
Python doesn't enforce any of this — being part of the interface is a design decision about what other code should be able to depend on, independent of what the language technically permits.

Practice: warm-up

For a Thermostat class with set_target(degrees), current_temperature(), and an internal attribute _sensor_reading, decide which members belong in its public interface and which are implementation details.

Stuck? Reveal one hint at a time.

  1. Hint 1

    Ask of each member: is this a promise about behavior other code should rely on, or a detail of how that behavior happens to be achieved right now?

  2. Hint 2

    A leading underscore, as in _sensor_reading, is a common (if unenforced) Python signal that something is not meant to be used from outside.

Reveal the classification

Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.

set_target(degrees)      -> public interface (a promised behavior)
current_temperature()    -> public interface (a promised behavior)
_sensor_reading          -> implementation detail (how the reading is obtained internally)

Practice: apply it

This code reaches directly into is_on_loan instead of using the public interface:

book = LibraryBook("Dune")

if not book.is_on_loan:
    book.is_on_loan = True
    print("Borrowed!")
else:
    print("Already on loan.")
What's the risk with writing it this way instead of calling book.borrow()?
Or reveal the answer without checking

Answer:It duplicates borrow()'s logic outside the class, so if the internal representation of loan status ever changes, this code silently stops working correctly
This code has re-implemented borrow()'s check-then-set logic using the internal attribute directly. If is_on_loan's meaning or existence ever changes, borrow() gets updated once — this code doesn't, and quietly breaks.

Modification challenge: rewrite this snippet to use book.borrow() and its return value instead of reading and setting is_on_loan directly.

Summary

  • An object’s interface is the behavior other code is meant to rely on — usually its methods; everything else is an implementation detail.
  • Code that only depends on an object’s interface keeps working when internals change; code that reaches into internal details doesn’t.
  • Python doesn’t enforce this distinction — treating something as internal is a design discipline, not a language guarantee.
  • When deciding what belongs in an interface, ask whether it’s a promise about behavior, not just whether Python happens to allow accessing it.