What:

It’s all about APIE:

It’s a way of programming based on objects. Objects can contain attributes or properties, or actions (functions or methods). For example:

  • A computer monitor has attributes of screen size.

Abstraction:

Only show the relevant stuff to the user of that object. EG: When the user wants to turn on the monitor, they likely won’t care about refresh rate. They should only be able to see the power button. Decoupling is crucial.

Below, the class Payment is simply outlining the different functionality it should have, but leaves the implementation to the subclasses.

from abc import ABC, abstractmethod
 
# Abstract class
class Payment(ABC):
    @abstractmethod
    def pay(self, amount):
        """Process the payment of the given amount"""
        pass
 
    @abstractmethod
    def refund(self, amount):
        """Process the refund of the given amount"""
        pass
 
# Concrete class for Credit Card payments
class CreditCardPayment(Payment):
    def __init__(self, card_number, card_holder_name):
        self.card_number = card_number
        self.card_holder_name = card_holder_name
 
    def pay(self, amount):
        # Simulate credit card payment processing
        print(f"Processing credit card payment of ${amount} for {self.card_holder_name}")
    
    def refund(self, amount):
        # Simulate refund to the credit card
        print(f"Refunding ${amount} to credit card {self.card_number}")
 
# Concrete class for PayPal payments
class PayPalPayment(Payment):
    def __init__(self, email):
        self.email = email
 
    def pay(self, amount):
        # Simulate PayPal payment processing
        print(f"Processing PayPal payment of ${amount} for {self.email}")
    
    def refund(self, amount):
        # Simulate refund to PayPal account
        print(f"Refunding ${amount} to PayPal account {self.email}")
 
# Client code using abstraction
def process_payment(payment_method: Payment, amount):
    payment_method.pay(amount)
 
def process_refund(payment_method: Payment, amount):
    payment_method.refund(amount)
 
# Example usage
credit_card = CreditCardPayment("1234 5678 9012 3456", "Alice Smith")
paypal = PayPalPayment("alice@example.com")
 
# Process payments using different methods
process_payment(credit_card, 100)     # Output: Processing credit card payment of $100 for Alice Smith
process_payment(paypal, 200)          # Output: Processing PayPal payment of $200 for alice@example.com

Inheritance

Allows a child class to inherit attributes and methods from a parent class. Inheritance follows the is-a relationship, for example: A Dog is-a Animal

class Animal:
    def __init__(self, name):
        self.name = name
 
    def make_sound(self):
        print(f"{self.name} makes a sound")
 
class Dog(Animal):  # Dog inherits from Animal
    def make_sound(self):
        print(f"{self.name} barks")
 
dog = Dog("Buddy")
dog.make_sound()  # Output: Buddy barks
 

Polymorphism

This refers to a different objects reacting to the same method call in different ways. If you have a big, general class with general functions and specific child classes with more specifically implemented functions, polymorphism allows the specific ones to override the general ones. Hard to explain, but the Java code should explain it better.

// Superclass: Animal
class Animal {
    // Method to be overridden in subclasses
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
    
    // Method overloading: Different versions of the same method
    public void sleep() {
        System.out.println("Animal sleeps for an unknown amount of time");
    }
 
    public void sleep(int hours) {
        System.out.println("Animal sleeps for " + hours + " hours");
    }
 
    public void sleep(int hours, String location) {
        System.out.println("Animal sleeps for " + hours + " hours at " + location);
    }
}
 
// Subclass: Dog
class Dog extends Animal {
    // Overriding makeSound method
    @Override
    public void makeSound() {
        System.out.println("Dog barks");
    }
}
 
// Subclass: Cat
class Cat extends Animal {
    // Overriding makeSound method
    @Override
    public void makeSound() {
        System.out.println("Cat meows");
    }
}
 
public class Main {
    public static void main(String[] args) {
        // Polymorphism: Using the superclass type to refer to subclass objects
        Animal myDog = new Dog();  // Dog object
        Animal myCat = new Cat();  // Cat object
 
        // Method overriding: Same method call results in different behavior
        myDog.makeSound();  // Output: Dog barks
        myCat.makeSound();  // Output: Cat meows
 
        // Method overloading: Same method name with different parameters
        Animal genericAnimal = new Animal();
        
        genericAnimal.sleep();  // Output: Animal sleeps for an unknown amount of time
        genericAnimal.sleep(8);  // Output: Animal sleeps for 8 hours
        genericAnimal.sleep(6, "the barn");  // Output: Animal sleeps for 6 hours at the barn
    }
}
 

Encapsulation

The idea of bundling attributes and functions into single classes, as well as restricting access to certain methods / attributes. The goal is to hide the inner workings, and only expose what is needed.

PS: It’s a crucial concept that University failed to properly teach me 😔 because of damn strikes.