Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Abstraction and Interfaces

Expose what a component can do, and hide the messy details of how it does it.

After classes, encapsulation, and inheritance, abstraction answers the next design question: what clean contract should the rest of the system depend on?


Why This Matters

A checkout page should be able to say process payment without caring whether the implementation uses card, wallet, or bank transfer. A reporting screen should be able to say export report without depending on CSV-specific details.

Abstraction helps teams:

  • design systems around capabilities rather than concrete implementations

  • swap implementations with minimal changes

  • test logic with lightweight fakes or mocks

  • reduce coupling across growing applications

Learning Goals

  • distinguish abstraction from encapsulation

  • use abstract base classes to define required behavior

  • explain what an interface-like contract means in Python

  • prepare for composition, where abstractions become reusable building blocks

Abstraction vs Encapsulation

These ideas work together, but they are not the same:

IdeaMain questionExample
EncapsulationHow do we protect valid state?BankAccount.withdraw() checks balance rules
AbstractionWhat capability should callers depend on?PaymentProcessor.process(amount) hides gateway details

Encapsulation protects the inside. Abstraction simplifies the outside.

Visual Intuition

Caption: The checkout workflow depends on a contract, not on one specific gateway implementation.*

Worked Example: Payment Processor Contract

The function complete_checkout() only needs a processor with the required behavior. That is the value of abstraction: fewer assumptions, more flexibility.

from abc import ABC, abstractmethod

class ReportExporter(ABC):
    @abstractmethod
    def export(self, records):
        pass

class CSVExporter(ReportExporter):
    def export(self, records):
        header = 'name,revenue'
        rows = [f'{name},{revenue}' for name, revenue in records]
        return '\n'.join([header] + rows)

class TextExporter(ReportExporter):
    def export(self, records):
        return '\n'.join(f'{name}: ${revenue:,.0f}' for name, revenue in records)

records = [('Retail', 42000), ('Subscriptions', 18500)]
for exporter in (CSVExporter(), TextExporter()):
    print(type(exporter).__name__)
    print(exporter.export(records))
    print('-' * 20)

Guided Practice

Quick MCQ

Which design choice best demonstrates abstraction?

  • A) A checkout function depends on PaymentProcessor.process() instead of one specific gateway class

  • B) A class exposes ten public variables and no methods

  • C) A programmer copies the same payment code into five files

Correct answer: A

Reflection Questions

  1. Why is it useful that complete_checkout() does not know which processor it receives?

  2. Which part of the system should change when a new payment gateway is added?

  3. How does abstraction reduce coupling between teams?

Answer Check
  1. It stays reusable and easier to test.

  2. Usually a new implementation class, not the checkout workflow itself.

  3. Teams can work against agreed contracts without depending on each other’s internal details.

Exercises

Exercise 1

Create a NotificationChannel abstraction with two implementations: EmailChannel and SMSChannel.

Exercise 2

Write a send_campaign(channel, message) function that works with either implementation without knowing channel details.

Exercise 3

Review one earlier class and separate its public contract from its internal helper methods. Which behaviors should callers know about, and which should remain internal?

Starter Hint

Begin by writing one abstract method such as send() or export(). Then implement two concrete subclasses and pass them into the same function.

Key Takeaway

Abstraction defines the clean surface area of a system. Callers depend on what an object can do, not on how that behavior is implemented.

This leads naturally to composition versus inheritance, where those abstractions become the seams that let us assemble flexible systems from smaller parts.