Introduction to OOP

This guide teaches Object-Oriented Programming (OOP) concepts to complete beginners using Python.

What is OOP?

Object-Oriented Programming is a programming paradigm that organizes code into objects.

An object combines data (attributes) and behavior (methods) together — just like things in the real world.

Core Benefits:

  • More organized and readable code

  • Easier to reuse and maintain

  • Better models real-world entities

Four Main Pillars of OOP:

  1. Encapsulation (hiding/securing data and methods)

  2. Containment (Has-a relationship)

  3. Inheritance (Is-a relationship)

  4. Polymorphism (Many forms)

OOP Basics - Classes and Objects

        classDiagram
    class Dog {
        String name
        int age
        bark()
        get_info()
    }
    

A Class is a blueprint. An Object is an actual instance created from that blueprint.

OOP in Python

class Dog:
    def __init__(self, name, age):
        self.name = name      # Attribute
        self.age = age        # Attribute

    def bark(self):
        print(f"{self.name} says Woof!")

    def get_info(self):
        return f"{self.name} is {self.age} years old."


# Creating objects
dog1 = Dog("Buddy", 5)
dog2 = Dog("Luna", 3)

dog1.bark()
print(dog2.get_info())

Encapsulation

Encapsulation means bundling data (attributes) and methods (functions) together inside a class, and protecting the internal data from direct outside access.

Think of it like a capsule or a remote control:

  • You only see the buttons (public interface)

  • The complex wiring inside is hidden (protected)

This helps prevent accidental damage to the data and makes code easier to manage.

        classDiagram
    class Player {
        int _health
        int __score
        take_damage()
        add_score()
        get_health()
    }
    note for Player "Private attributes
    (start with _ or __)
    are hidden from outside"
    

Why is Encapsulation Important?

  • Protects data from invalid changes

  • Makes code more secure and maintainable

  • Allows you to change internal implementation without breaking other code

Encapsulation in Python

class Player:
    def __init__(self, name):
        self.name = name
        self._health = 100        # Protected attribute (convention)
        self.__score = 0          # Private attribute (name mangling)

    # Public method - controlled way to change health
    def take_damage(self, damage: int):
        if damage > 0:
            self._health -= damage
            print(f"{self.name} took {damage} damage!")
            if self._health <= 0:
                print(f"{self.name} has been defeated!")
                self._health = 0

    # Public method to safely get health
    def get_health(self) -> int:
        return self._health

    # Public method to add score
    def add_score(self, points: int):
        if points > 0:
            self.__score += points

    def get_score(self) -> int:
        return self.__score


# Using the class
player1 = Player("Alex")

player1.take_damage(30)
print(f"Health: {player1.get_health()}")      # Safe access
player1.add_score(250)
print(f"Score: {player1.get_score()}")

# Direct access to private attribute is discouraged:
# print(player1.__score)   # This will not work easily

Containment

Containment means one class contains another class. It represents a “Has-a” relationship.

Example: A Car has an Engine.

        classDiagram
    class Engine {
        int horsepower
        start()
    }
    class Car {
        String make
        String model
        Engine engine
        start_car()
    }
    Car o-- Engine : contains
    

Containment in Python

class Engine:
    def __init__(self, horsepower):
        self.horsepower = horsepower

    def start(self):
        print(f"Engine ({self.horsepower}hp) is roaring to life!")


class Car:
    def __init__(self, make, model, horsepower):
        self.make = make
        self.model = model
        self.engine = Engine(horsepower)   # Containment

    def start_car(self):
        print(f"Starting {self.make} {self.model}")
        self.engine.start()


my_car = Car("Tesla", "Model 3", 350)
my_car.start_car()

Inheritance

Inheritance allows a new class to inherit properties and methods from an existing class. It represents an “Is-a” relationship.

        classDiagram
    Animal <|-- Dog
    Animal <|-- Cat

    class Animal {
        String name
        eat()
        sleep()
    }
    class Dog {
        String breed
        bark()
    }
    class Cat {
        meow()
    }
    

Inheritance in Python

class Animal:
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f"{self.name} is eating.")

    def sleep(self):
        print(f"{self.name} is sleeping.")


class Dog(Animal):                    # Inheritance
    def __init__(self, name, breed):
        super().__init__(name)        # Call parent constructor
        self.breed = breed

    def bark(self):
        print(f"{self.name} says Woof!")


class Cat(Animal):                    # Inheritance
    def meow(self):
        print(f"{self.name} says Meow!")


my_dog = Dog("Max", "Golden Retriever")
my_cat = Cat("Whiskers")

my_dog.eat()      # Inherited method
my_dog.bark()     # Dog's own method

Polymorphism

Polymorphism means “many forms”. It allows objects of different classes to be used interchangeably if they share a common interface.

The same method name can behave differently depending on the object.

        classDiagram
    Shape <|-- Rectangle
    Shape <|-- Circle

    class Shape {
        area()
    }
    class Rectangle {
        area()
    }
    class Circle {
        area()
    }
    

Polymorphism in Python

class Shape:
    def area(self):
        return 0


class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):                     # Overriding
        return self.width * self.height


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):                     # Overriding
        return 3.14159 * self.radius ** 2


# Polymorphism in action
shapes = [
    Rectangle(5, 3),
    Circle(4),
    Rectangle(6, 2)
]

for shape in shapes:
    print(f"Area = {shape.area():.2f}")

Key Point: Even though all are different shapes, we can call .area() on any of them uniformly.

Summary

  • OOP → Organize code using classes and objects

  • Encapsulation → Prevents certain attributes and methods to be used externally (outside the class’s own methods)

  • Containment → “Has-a” relationship (one object owns another)

  • Inheritance → “Is-a” relationship (code reuse through hierarchy)

  • Polymorphism → Same method, different behavior

These four concepts together make your programs more powerful, flexible, and easier to maintain.