Skip to content
My Subject Hub
Free class
Object-oriented programming

OOP concepts explained with one running example

Object-oriented programming is easier when every idea is shown on the same example. We build a small library system step by step and name each OOP concept as it appears.

By Ali Sher AbbasiPublished 4 min read

Object-oriented programming (OOP) is a way of organising code around things rather than around steps. Instead of one long list of instructions, you describe the things in your program (a book, a member, a loan), what each one knows, and what each one can do. Most OOP courses list four "pillars": encapsulation, inheritance, polymorphism and abstraction. They make far more sense when you see them solve real problems in one program, so that is what we will do.

The examples are in Python because it is short to read. The same ideas apply in Java and C++, and we point out the differences as we go.

Classes and objects

A class is a blueprint. An object is one thing built from that blueprint. A library has one idea of what a book is (the class) and thousands of actual books (objects).

library.py
class Book:
    def __init__(self, title, author):
        self.title = title          # data each book carries
        self.author = author
        self.on_loan = False

    def describe(self):             # something each book can do
        return f"{self.title} by {self.author}"


first = Book("Clean Code", "Robert C. Martin")
second = Book("Python Crash Course", "Eric Matthes")
print(first.describe())

__init__ is the constructor: it runs once when each object is created and gives it its starting data. first and second are two separate objects with their own title, author and on_loan values.

Encapsulation: protect the object's data

Right now any part of the program can write first.on_loan = True without checking anything, including lending a book that is already out. Encapsulation means keeping the data inside the object and only changing it through methods that enforce the rules.

library.py
class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self._on_loan = False       # "internal": change it through methods

    def lend(self):
        if self._on_loan:
            raise ValueError(f"{self.title} is already on loan")
        self._on_loan = True

    def give_back(self):
        self._on_loan = False

    @property
    def available(self):
        return not self._on_loan

Now there is exactly one place where a loan can happen, and it checks the rule every time. In Python the underscore is a convention that says "do not touch this from outside". Java and C++ enforce it with the private keyword, and the compiler refuses code that breaks the rule.

Inheritance: share what is common

The library also lends DVDs. A DVD has a title and can be lent and returned, just like a book, but it has a running time instead of an author. Inheritance lets a new class reuse an existing one and add or change only what is different.

library.py
class Item:
    loan_days = 14

    def __init__(self, title):
        self.title = title
        self._on_loan = False

    def lend(self):
        if self._on_loan:
            raise ValueError(f"{self.title} is already on loan")
        self._on_loan = True

    def describe(self):
        return self.title


class Book(Item):
    def __init__(self, title, author):
        super().__init__(title)     # let Item set up the shared parts
        self.author = author

    def describe(self):
        return f"{self.title} by {self.author}"


class Dvd(Item):
    loan_days = 7

    def __init__(self, title, minutes):
        super().__init__(title)
        self.minutes = minutes

    def describe(self):
        return f"{self.title} ({self.minutes} min)"

Book and Dvd both inherit lend from Item, so the loan rule is written once. Dvd also overrides loan_days, because DVDs go back sooner. The test for inheritance is the phrase "is a": a book is an item, and a DVD is an item.

Polymorphism: one call, the right behaviour

Polymorphism means "many forms": the same method call does the right thing for each kind of object. The code that prints the catalogue does not need to know whether it is holding a book or a DVD.

library.py
catalogue = [
    Book("Clean Code", "Robert C. Martin"),
    Dvd("Planet Earth II", 298),
]

for item in catalogue:
    print(item.describe(), "-", item.loan_days, "days")

# Clean Code by Robert C. Martin - 14 days
# Planet Earth II (298 min) - 7 days

When the library adds magazines, you write a Magazine class with its own describe, and this loop works without a single change. That is the practical value of polymorphism: new kinds of objects fit into old code.

Abstraction: show what, hide how

Abstraction means exposing a simple interface and hiding the details. A librarian calls item.lend() without knowing how loans are stored. You can go further and make Item an abstract class: one that cannot be created on its own and that requires every subclass to provide certain methods.

library.py
from abc import ABC, abstractmethod


class Item(ABC):
    # ... the same shared code as before ...

    @abstractmethod
    def describe(self):
        """Every kind of item must say how it is described."""


Item("Mystery")   # TypeError: Can't instantiate abstract class Item ...

In Java this is an abstract class or an interface; in C++ it is a class with a pure virtual function (virtual std::string describe() = 0;).

Mistakes students often make

  • Inheriting when you should combine. A library has members; it is not a kind of member. Use inheritance only for "is a". For "has a", store one object inside another (this is called composition).
  • Making everything public. If any code can change any field, you lose the main benefit of OOP. Start with data private and add methods as you need them.
  • Classes that only hold data. If all the logic lives outside the class in long functions, the code is still procedural. Move behaviour next to the data it uses.
  • Memorising definitions instead of using them. Exams and interviews ask you to apply the concepts. Practise by modelling small systems: a bank account, a school timetable, a shop basket.

Practise it yourself

  1. Add a Magazine class with an issue number and a three-day loan.
  2. Add a Member class that can borrow at most three items at once. Where should that rule live?
  3. Rewrite Item and Book in Java with private fields and getters.

If you would like someone to review your answers, or you are preparing for an OOP exam or a university project in Java, C++ or Python, our OOP tutoring is one-to-one and starts with a free class. When OOP feels comfortable, data structures and algorithms is the natural next step.

One-to-one live classes on this topic. The first class is free.

More guides

  • Programming for beginners

    Which programming language should you learn first?

    Your first language matters less than you think, but the right one for your goal saves weeks of frustration. Here is how to choose, by what you want to do next.

    4 min read

  • Data Structures and Algorithms

    How to learn Data Structures and Algorithms from scratch

    Data Structures and Algorithms feels huge until you learn it in the right order. Here is what to know first, a topic sequence that builds on itself, and a way to practise that works.

    4 min read

All guides