Updated Lecture Notes On Object Oriented Programming-1
Updated Lecture Notes On Object Oriented Programming-1
OOP aims to provide a structured, modular approach to software development by combining data
and the operations on that data into objects. This paradigm encourages practices such as data
abstraction, encapsulation, inheritance, and polymorphism, which lead to better organized, more
reusable, and maintainable code.
By modeling entities as objects, developers can represent real-world elements in code (such as
people, cars, or bank accounts) and interact with these objects through their attributes and
methods.
---
# Class
A class is a blueprint or template for creating objects. It defines the structure and behavior that
the objects created from it will have. Classes typically include:
- Attributes: These are the characteristics or properties of the object (e.g., name, age, color).
- Methods: These are the functions that describe the behaviors of the object (e.g., walking,
driving, calculating).
Classes provide a way to bundle data and functionality together. For example, you could define a
`Car` class with attributes like `make`, `model`, and `year`, and methods like `start_engine()` or
`drive()`.
1
Example in Python:
```python
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start_engine(self):
print(f"{self.year} {self.make} {self.model} engine starts.")
```
# Object
An object is an instance of a class. When a class is defined, no memory is allocated until an
object is created from that class. Objects are the fundamental building blocks of an OOP system
and represent actual entities in a program.
```python
my_car = Car('Toyota', 'Corolla', 2020)
my_car.start_engine() # Outputs: 2020 Toyota Corolla engine starts.
```
Here, `my_car` is an object of the `Car` class with specific attributes (`make`, `model`, and
`year`) and the ability to execute the `start_engine` method.
# Attributes
Attributes, also called fields or properties, are the data variables that store information about an
object. Each object has its own set of attribute values. These attributes are defined in the class
but are specific to individual instances (objects) created from that class.
For example, in the `Car` class, the attributes include `make`, `model`, and `year`.
```python
# Accessing the attributes of an object
print(my_car.make) # Outputs: Toyota
print(my_car.year) # Outputs: 2020
```
2
# Methods
Methods are functions that belong to a class and describe the actions that an object can perform.
These methods are typically used to manipulate object attributes or perform operations related to
the object.
In the `Car` class, the `start_engine` method allows the car to perform the action of starting the
engine.
```python
# Calling a method on an object
my_car.start_engine() # Outputs: 2020 Toyota Corolla engine starts.
```
---
# Modularity
In OOP, code is divided into distinct classes and objects, making the software modular. Each
class is independent and can be developed, tested, and maintained separately from other classes.
This modularity helps in managing complex systems, as changes to one part of the system can be
made without affecting other parts.
For example, if a class responsible for handling database operations needs changes, those
changes can be isolated in that specific class without affecting other parts of the system.
# Reusability
Classes and objects can be reused across multiple projects, reducing duplication of code. Once a
class is written and tested, it can be used to create multiple objects or reused in other
applications. OOP promotes code reuse through:
- Inheritance, where new classes can inherit properties and behaviors from existing ones, and
- Composition, where classes can contain objects from other classes to extend functionality.
This reduces development time and ensures consistency across different parts of the program.
# Scalability
OOP systems are easier to scale. As projects grow in size and complexity, new features can be
added by creating new classes or extending existing ones through inheritance. This enables the
system to evolve while keeping the codebase clean and maintainable.
3
For example, if you want to add a new type of vehicle (e.g., `Truck`) to a system that already has
a `Car` class, you can create a `Truck` class that inherits from `Vehicle`, leveraging the existing
functionality and adding new behaviors specific to trucks.
# Maintainability
OOP's modular structure also makes it easier to maintain. Since the code is divided into
independent classes, bug fixes or changes can be made in one class without affecting the entire
system. This encapsulation ensures that internal implementations are hidden from the user, so as
long as the interface (public methods) remains the same, other parts of the system can interact
with the object as before.
In addition, the use of well-defined interfaces and clear separation of concerns (through
encapsulation) results in more understandable and maintainable codebases. For large projects,
this is a significant advantage because it reduces the likelihood of introducing bugs when making
changes.
Encapsulation is one of the core principles of OOP that involves bundling data (attributes) and
methods (functions) that operate on that data into a single unit, typically a class. Encapsulation
helps to protect the integrity of the data by restricting access and hiding implementation details.
It ensures that an object's internal state is shielded from unauthorized access or modification,
allowing control over how data is accessed and modified.
In encapsulation, data and methods are grouped together within a class, and access to this data is
controlled through access modifiers:
- Private: Data and methods are only accessible from within the class itself. This prevents outside
code from modifying the internal state of the object directly.
- Public: Data and methods are accessible from outside the class. Public members can be
accessed and modified by any code that creates an instance of the class.
- Protected: Data and methods are accessible within the class and its subclasses (classes that
inherit from the base class). Protected members are not accessible outside the class hierarchy.
# Benefits of Encapsulation:
- Data Security: By restricting direct access to an object's internal state, encapsulation helps
protect sensitive data.
- Modularity: Each class can be developed, tested, and maintained separately.
- Maintainability: Internal changes to the class do not affect other parts of the program as long as
the public interface remains unchanged.
4
class Employee:
def __init__(self, name, salary):
self.name = name # Public attribute
self.__salary = salary # Private attribute (indicated by double underscores)
---
2.2 Abstraction
Abstraction refers to the concept of hiding the internal complexity of a system and exposing only
the necessary parts to the user. It allows developers to focus on *what* an object does rather than
*how* it does it. By providing a simplified interface, abstraction reduces complexity and
enhances usability.
In OOP, abstraction is often achieved through abstract classes and interfaces. An abstract class is
a class that cannot be instantiated on its own and may contain abstract methods (methods without
implementation). Subclasses that inherit from abstract classes must provide implementations for
these methods.
# Benefits of Abstraction:
5
- Simplicity: Users interact with an object using a simplified interface, hiding complex
implementation details.
- Code Reusability: Abstraction promotes reusable code by allowing different implementations
of the same interface.
- Maintainability: By separating implementation details from the user, abstraction makes it easier
to modify and extend the system without impacting other components.
# Abstract class
class Animal(ABC):
# Abstract method
@abstractmethod
def sound(self):
pass
class Cat(Animal):
def sound(self):
return "Meow"
---
2.3 Inheritance
Inheritance is a mechanism in OOP that allows a class (called a *subclass* or *derived class*) to
inherit properties and methods from another class (called a *superclass* or *base class*). This
6
promotes code reuse and establishes a natural hierarchy between classes. Inheritance allows
subclasses to:
- Inherit attributes and methods from the superclass.
- Extend or override the functionality of the superclass by adding new methods or modifying
existing ones.
# Types of Inheritance:
- Single Inheritance: A subclass inherits from one superclass.
- Multiple Inheritance: A subclass inherits from multiple superclasses (supported in some
languages like Python).
- Multilevel Inheritance: A subclass inherits from a superclass, which in turn inherits from
another superclass, forming a chain of inheritance.
# Benefits of Inheritance:
- Code Reusability: Inheritance allows for reuse of existing code, reducing duplication.
- Maintainability: Modifications made to the superclass automatically propagate to subclasses.
- Extensibility: Subclasses can extend the functionality of the superclass by adding new features.
def start(self):
print(f"{self.make} {self.model} is starting.")
7
---
2.4 Polymorphism
Polymorphism refers to the ability of methods to take on different forms depending on the object
that invokes them. It enables the same method to behave differently depending on the context.
Polymorphism promotes flexibility and dynamic method invocation in OOP.
# Benefits of Polymorphism:
- Flexibility: Polymorphism allows methods to behave differently depending on the object
invoking them.
- Code Reusability: The same method can be reused across different classes with different
behaviors, reducing code duplication.
- Extensibility: New classes can override inherited methods to implement new behavior without
modifying the original class.
class Dog(Animal):
def sound(self):
print("Dog barks")
class Cat(Animal):
def sound(self):
print("Cat meows")
# Using polymorphism
animals = [Dog(), Cat()]
8
```
Dog barks
Cat meows
```
In this example, the `sound()` method behaves differently depending on the object (`Dog` or
`Cat`) that invokes it. This is an example of method overriding in action.
---
9
An object is an instance of a class. Objects are created by calling the class constructor. In
Python, you create an object by calling the class name as if it were a function, passing in any
arguments required by the __init__ method.
Example: Creating Objects in Python
python
Copy code
# Creating an object of the Car class
my_car = Car('Toyota', 'Corolla', 2020)
10
Example:
python
Copy code
class Car:
wheels = 4 # Class attribute
print(my_car.wheels) # Output: 4
print(your_car.wheels) # Output: 4
In this example, the wheels attribute is a class attribute, and it is shared across all instances of the
Car class. Both my_car and your_car have the same value for wheels.
Key Differences:
Instance attributes are specific to each object.
Class attributes are shared by all instances of the class.
def start_engine(self):
print(f"The {self.year} {self.make} {self.model} engine starts.")
11
methods are typically used for utility or helper functions that do not require access to object-
specific data.
In Python, static methods are created using the @staticmethod decorator.
Example:
python
Copy code
class MathOperations:
@staticmethod
def add(a, b):
return a + b
class Subclass(Superclass):
# Attributes and methods of the subclass
When a subclass inherits from a superclass:
It automatically gets all the attributes and methods of the superclass.
The subclass can add new attributes and methods, or override existing ones.
12
Example 1: Basic Inheritance
Let’s look at an example of inheritance where the subclass Car inherits from the superclass
Vehicle:
python
Copy code
class Vehicle:
def __init__(self, make, model):
self.make = make # Attribute: manufacturer of the vehicle
self.model = model # Attribute: model of the vehicle
def start(self):
print(f"Starting the {self.make} {self.model}")
def stop(self):
print(f"Stopping the {self.make} {self.model}")
13
Syntax of Method Overriding:
To override a method, you simply define a method in the subclass with the same name and
parameters as the method in the superclass.
python
Copy code
class Superclass:
def method(self):
# Original implementation
class Subclass(Superclass):
def method(self):
# New, overridden implementation
When the method is called on an object of the subclass, the overridden version will be executed.
Example 1: Basic Method Overriding
Let’s extend the previous example by overriding the start() method in a subclass:
python
Copy code
class ElectricCar(Car): # ElectricCar is a subclass of Car
def start(self): # Overriding the start method
print(f"The electric {self.make} {self.model} starts silently.")
In this example:
ElectricCar is a subclass of Car.
It overrides the start() method inherited from Vehicle to provide a new implementation
specific to electric cars.
Example 2: Creating Objects from the Subclass with Method Overriding
python
Copy code
# Creating an object of the ElectricCar class
my_electric_car = ElectricCar('Tesla', 'Model S')
14
Copy code
class ElectricCar(Car):
def start(self):
super().start() # Call the start method from the Vehicle class
print(f"The electric {self.make} {self.model} makes no noise when starting.")
Here:
The super().start() call invokes the start() method from the superclass Vehicle, so the
original functionality is preserved.
After calling the superclass method, the ElectricCar adds its own message about starting
silently.
Types of Inheritance
Inheritance in OOP can take several forms, depending on how classes relate to each other:
1. Single Inheritance: A subclass inherits from one superclass.
o Example: Car inherits from Vehicle.
2. Multiple Inheritance: A subclass inherits from multiple superclasses (supported in some
programming languages like Python).
o Example: FlyingCar might inherit from both Vehicle and Airplane.
3. Multilevel Inheritance: A subclass inherits from another subclass, creating a chain of
inheritance.
o Example: ElectricCar inherits from Car, which inherits from Vehicle.
4. Hierarchical Inheritance: Multiple subclasses inherit from the same superclass.
o Example: Car, Truck, and Motorcycle all inherit from Vehicle.
4.3 Advantages of Inheritance
1. Code Reusability: Inheritance promotes the reuse of code. The subclass can inherit
methods and attributes from the superclass without duplicating code.
2. Extensibility: New features and behaviors can be added to a subclass without modifying
the original superclass, allowing for flexible and scalable systems.
3. Modularity: By organizing code into a hierarchy of classes, inheritance helps in breaking
down complex problems into simpler, related components.
4. Polymorphism: Inheritance allows polymorphism, which means that a subclass can
define methods that share the same name as methods in the superclass but behave
differently. This increases flexibility and dynamism in code execution.
15
In languages like Python, method overloading is not natively supported in the same way as in
Java or C++. Instead, developers typically achieve similar functionality by using default
arguments or variable-length arguments. However, for educational purposes, we can illustrate
how method overloading would look conceptually.
```python
class Calculator:
def add(self, a, b): # Method with 2 parameters
return a + b
In the example above, the second `add` method definition effectively overrides the first due to
the same method name, resulting in a conflict. To properly implement method overloading in
Python, you would use default parameters or variable-length arguments:
```python
class Calculator:
def add(self, *args): # Accepts variable-length arguments
return sum(args)
calc = Calculator()
print(calc.add(2, 3)) # Outputs: 5
print(calc.add(2, 3, 4)) # Outputs: 9
```
Here, the `add` method can handle any number of arguments, providing a polymorphic behavior
that adapts based on the input.
16
```python
class Animal:
def sound(self):
return "Some sound" # General sound
class Dog(Animal):
def sound(self): # Overriding the sound method
return "Bark" # Specific sound for Dog
class Cat(Animal):
def sound(self): # Overriding the sound method
return "Meow" # Specific sound for Cat
In this example:
- The `Animal` class defines a general `sound` method.
- Both `Dog` and `Cat` classes override the `sound` method to provide specific implementations.
- When iterating through a list of `Animal` objects, the overridden method in each subclass is
invoked, demonstrating run-time polymorphism.
---
# Chapter 6: Encapsulation and Data Hiding
Encapsulation is another fundamental principle of OOP that involves bundling data (attributes)
and methods (functions) that operate on the data into a single unit known as a class. This concept
also restricts direct access to some of an object’s components, leading to greater data integrity
and abstraction.
6.1 Implementing Encapsulation
Encapsulation is often implemented by making attributes private and providing public methods
to modify or access these attributes. By doing this, we control how the data is accessed and
modified.
# Example of Encapsulation
```python
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private attribute
17
def get_balance(self):
return self.__balance # Method to access the private attribute
# Usage
account = BankAccount(1000)
account.deposit(500)
print(account.get_balance()) # Output: 1500
```
In this example:
- The `__balance` attribute is private, denoted by the double underscores, meaning it cannot be
accessed directly from outside the class.
- The methods `deposit` and `get_balance` provide controlled access to modify and retrieve the
balance.
6.2 Access Modifiers
Access modifiers are keywords used to specify the accessibility of class members. In Python, we
commonly use three access modifiers:
- Private: Attributes and methods that cannot be accessed from outside the class (e.g.,
`__attribute`).
- Protected: Attributes and methods intended to be accessible only within the class and its
subclasses (e.g., `_attribute`).
- Public: Attributes and methods that are accessible from outside the class.
example = Example()
print(example.public_attribute) # Accessible
print(example._protected_attribute) # Accessible, but should be treated as private
# print(example.__private_attribute) # Raises an AttributeError
```
1. Modularity: OOP promotes organizing code into separate objects, making it easier to manage
and understand.
18
2. Code Reusability: Classes and objects can be reused across different projects, reducing
duplication and development time.
3. Scalability: New features can be added by creating new classes or extending existing ones,
making systems more flexible and scalable.
4. Maintainability: The modular nature of OOP makes it easier to update and maintain code, as
changes can be made in isolation without affecting other components.
2. Performance: OOP can lead to performance overhead due to abstraction layers and the added
complexity of managing objects and their interactions.
3. Overhead: Maintaining multiple classes and objects can sometimes lead to increased memory
usage and performance issues, particularly in large-scale applications.
19