Object-Oriented Programming Introduction CL-20

Object-Oriented Programming Introduction 

Object-Oriented Programming (OOP):

Object-Oriented Programming, often abbreviated as OOP, is a programming paradigm that focuses on organizing and structuring code based on objects. In OOP, everything is treated as an object, which can have data (attributes) and actions (methods) associated. OOP is essential in software development because it offers several benefits that make it easier to manage and maintain complex code.

Object-Oriented Programming Example (in Python):

# Define a class class BankAccount: def __init__(self, balance, interest_rate): self.balance = balance self.interest_rate = interest_rate def calculate_interest(self): return self.balance * self.interest_rate # Create an object account = BankAccount(1000, 0.05) # Usage result = account.calculate_interest() print(result) #output 50

In OOP, we define a class (BankAccount) that bundles data (balance, interest_rate) and methods (calculate_interest) together. We create objects (account) from this class to work with the data and methods.

Why OOP is Important in Software Development:


OOP promotes modularity, which means breaking down a large program into smaller, more manageable pieces (objects). Each object can be developed, tested, and maintained independently, making it easier to collaborate in large development teams.


Objects in OOP can be reused in different parts of your program or even in entirely different programs. This reusability can save time and reduce the likelihood of errors since you can rely on well-tested objects.

Classes and Objects:

Python classes are blueprints for creating objects. Objects are instances of these classes that can store data (attributes) and perform actions (methods).
Example and Output:
# Define a simple class class Dog: def __init__(self, name): self.name = name def bark(self): return f"{self.name} says Woof!" # Create objects (instances) of the Dog class dog1 = Dog("Buddy") dog2 = Dog("Milo") # Access object attributes and call methods print(dog1.name) # Output: Buddy print(dog2.bark()) # Output: Milo says Woof!

In this example, we define a Dog class with an __init__ method to initialize the name attribute and a bark method. We create two Dog objects (dog1 and dog2) and access their attributes and methods, resulting in the given output.


Encapsulation means bundling an object's data and methods together into a single unit, hiding the internal details from the outside world. This helps prevent unintended data modifications and allows for better control over access to an object's properties.
Here's an example:
class BankAccount: def __init__(self, account_number, balance): self.__account_number = account_number # Private attribute self._balance = balance # Protected attribute def deposit(self, amount): if amount > 0: self._balance += amount print(f"Deposited ${amount}. New balance: ${self._balance}") else: print("Invalid deposit amount.") def withdraw(self, amount): if amount > 0 and amount <= self._balance: self._balance -= amount print(f"Withdrew ${amount}. New balance: ${self._balance}") else: print("Invalid withdrawal amount or insufficient funds.") def get_balance(self): return self._balance def get_account_info(self): return f"Account Number: {self.__account_number}, Balance: ${self._balance}" # Create a bank account object account = BankAccount("123456789", 1000) # Accessing private and protected attributes (usually not recommended) print(account._balance) # Output: 1000 # print(account.__account_number) # This would result in an error (NameError) # Using public methods to interact with the object account.deposit(500) # Output: Deposited $500. New balance: $1500 account.withdraw(200) # Output: Withdrew $200. New balance: $1300 # Accessing object information through a public method print(account.get_balance()) # Output: 1300 print(account.get_account_info()) # Output: Account Number: 123456789, Balance: $1300

In this example, the BankAccount class encapsulates the account_number and _balance attributes. account_number is marked as private using double underscores (__), and _balance is marked as protected using a single underscore (_). While Python allows access to these attributes, it is generally recommended to use public methods (e.g., deposit, withdraw) to interact with the object, as shown in the example.


Abstraction involves simplifying complex reality by modeling classes based on real-world entities. For example, you can model a "Car" class with properties like "color" and "speed" and methods like "start" and "stop." This abstraction makes the code more understandable and manageable.
Here's an example of abstraction:
from abc import ABC, abstractmethod # Abstract base class (ABC) representing a shape class Shape(ABC): def __init__(self, name): self.name = name @abstractmethod def area(self): pass @abstractmethod def perimeter(self): pass # Concrete subclass representing a Circle class Circle(Shape): def __init__(self, name, radius): super().__init__(name) self.radius = radius def area(self): return 3.14159 * self.radius**2 def perimeter(self): return 2 * 3.14159 * self.radius # Concrete subclass representing a Square class Square(Shape): def __init__(self, name, side_length): super().__init__(name) self.side_length = side_length def area(self): return self.side_length**2 def perimeter(self): return 4 * self.side_length # Create objects (instances) of Circle and Square circle = Circle("Circle", 5) square = Square("Square", 4) # Access and use abstract methods through concrete objects print(f"{circle.name}: Area = {circle.area()}, Perimeter = {circle.perimeter()}") print(f"{square.name}: Area = {square.area()}, Perimeter = {square.perimeter()}")

Circle: Area = 78.53975, Perimeter = 31.4159 Square: Area = 16, Perimeter = 16

In this example, we define an abstract base class Shape with abstract methods area and perimeter. Concrete subclasses Circle and Square inherit from Shape and provide concrete implementations of these abstract methods.

By using abstraction, we model shapes as objects with well-defined properties and behaviors (area and perimeter) while abstracting away the complex mathematical details of each shape. This simplifies the code and allows us to work with shapes in a more intuitive and understandable way.


OOP supports inheritance, where you can create new classes (subclasses) that inherit properties and methods from existing classes (superclasses). This promotes code reuse and the creation of specialized classes.
Here's an example of inheritance in Python:
# Base class (superclass) representing a Vehicle class Vehicle: def __init__(self, make, model): self.make = make self.model = model def start_engine(self): return f"{self.make} {self.model}'s engine started." # Subclass representing a Car, inheriting from Vehicle class Car(Vehicle): def __init__(self, make, model, num_doors): super().__init__(make, model) self.num_doors = num_doors def honk(self): return f"{self.make} {self.model} honks the horn." # Subclass representing a Motorcycle, also inheriting from Vehicle class Motorcycle(Vehicle): def __init__(self, make, model, num_wheels): super().__init__(make, model) self.num_wheels = num_wheels def rev_engine(self): return f"{self.make} {self.model} revs the engine." # Create objects (instances) of Car and Motorcycle car = Car("Toyota", "Camry", 4) motorcycle = Motorcycle("Harley-Davidson", "Sportster", 2) # Access inherited methods and subclass-specific methods print(car.start_engine()) # Output: Toyota Camry's engine started. print(car.honk()) # Output: Toyota Camry honks the horn. print(motorcycle.start_engine()) # Output: Harley-Davidson Sportster's engine started. print(motorcycle.rev_engine()) # Output: Harley-Davidson Sportster revs the engine.

In this example, we have a base class Vehicle with a method start_engine. We then create two subclasses, Car and Motorcycle, both of which inherit from the Vehicle class. Each subclass adds its own unique methods (honk for Car and rev_engine for Motorcycle) while inheriting the start_engine method from the base class.

By using inheritance, we can reuse code and model the relationships between different types of vehicles in a more organized and efficient manner.


Polymorphism allows objects of different classes to be treated as objects of a common superclass. This flexibility simplifies code and enables you to write more generic functions that work with various objects.
Here's an example:
# Base class representing an Animal class Animal: def __init__(self, name): self.name = name def speak(self): pass # Subclass representing a Dog class Dog(Animal): def speak(self): return f"{self.name} says Woof!" # Subclass representing a Cat class Cat(Animal): def speak(self): return f"{self.name} says Meow!" # Function that interacts with any Animal object def make_animal_speak(animal): return animal.speak() # Create objects (instances) of Dog and Cat dog = Dog("Buddy") cat = Cat("Whiskers") # Use the make_animal_speak function with different objects print(make_animal_speak(dog)) # Output: Buddy says Woof! print(make_animal_speak(cat)) # Output: Whiskers says Meow!

In this example, we have a base class Animal with a method speak. We create two subclasses, Dog and Cat, both of which inherit from the Animal class and override the speak method with their own implementations.

The make_animal_speak function takes an Animal object as its argument and calls the speak method on it. Because of polymorphism and method overriding, the function can work with different types of animals (in this case, Dog and Cat) without needing to know their specific types, resulting in different outputs based on the actual object type.

This flexibility in handling objects of different classes as if they were objects of a common superclass is the essence of polymorphism.

Comparison with Procedural Programming:

In contrast to OOP, procedural programming is another programming paradigm. Here's a simple comparison using a common example:

Procedural Programming Example (in pseudo-code):
# Define data balance = 1000 interest_rate = 0.05 # Perform actions def calculate_interest(balance, interest_rate): return balance * interest_rate # Usage result = calculate_interest(balance, interest_rate) print(result) #output: 50

In procedural programming, we use functions to manipulate data. Data and functions are separate.

In summary, OOP promotes code organization, reusability, and abstraction by modeling real-world entities as objects. It allows for better control over data and methods through encapsulation and offers features like inheritance and polymorphism for building flexible and maintainable software systems. Comparatively, procedural programming focuses on procedures and functions to manipulate data without bundling them into objects.

Post a Comment

Previous Post Next Post