Object-Oriented Programming
Published: 2023-02-16 06:28:54

## Class
A class is the blueprint or the prototype from which the objects are being created.It contains some attributes and methods.
```{python}
class Person:
    def __init__(self, name):
        self.name = name

    def talk(self):
        print(f"Hey, I am {self.name}")

```
## Object
The object is an entity that has a state and behavior associated with it. It may be any real-world object like a table,dog,car,etc.
An object has attributes and methods in it.
```{python}
class Person:
    def __init__(self, name):
        self.name = name

    def talk(self):
        print(f"Hey, I am {self.name}")

p_1 = Person('raina')
print(p_1.name)
p_1.talk()
```
## \_\_init\_\_ method
The \_\_init\_\_ method is similar to constructor. It is executed as an object of a class is created. The method is useful to do any initialization to the object.

## self
self represents the instance of a class.
self allows to access variables,attributes,and methods of a defined class in python.
self must always be the first argument of any class method.

## Inheritance
Inheritance is the capability of one class to derive or inherit the properties from another class. The class that derives properties is called the derived class or child class and the class from which the properties are being derived is called the base class or parent class.
**Types of Inheritance:**
**Single Inheritance**
Single-level inheritance enables a derived class to inherit characteristics from a single-parent class. 
```{python}
# parent class
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def details(self):
        print(f"I am {self.name}")
        print(f"My id is {self.age}")


# child class
class Employee(Person):
    def __init__(self, name, age, pay, job):
        self.pay = pay
        self.job = job
        Person.__init__(self,name, age)

    def details(self):
        super().details()
        print(f"My job is {self.job}")
        print(f"My salary is {self.pay}")

Emp_1 = Employee('raina',24,10000,"Software")
Emp_1.details()
```
**Multilevel Inheritance**
Multi-level inheritance enables a derived class to inherit properties from an immediate parent class which in turn inherits properties from his parent class.
```{python}
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def details(self):
        print(f"I am {self.name}")
        print(f"My id is {self.age}")

    def fun(self):
        print("Inside Person class")


class Employee(Person):
    def __init__(self,name,age,pay, job):
        self.pay = pay
        self.job = job
        Person.__init__(self,name,age)

    def details(self):
        super().details()
        print(f"My job is {self.job}")
        print(f"My salary is {self.pay}")

    def run(self):
        print("Inside Employee class")


class Manager(Employee):
    def __init__(self,name,age,pay,job,exp):
        self.exp = exp
        Employee.__init__(self,name,age,pay,job)

    def details(self):
        super().details()
        print(f"Experience : {self.exp}")


Man_1 = Manager('raina',24,1000,"Software",5)
Man_1.details()
Man_1.fun()
Man_1.run()
```
**Hierarchical Inheritance**
Hierarchical level inheritance enables more than one derived class to inherit properties from a parent class.
```{python}
class Vehical:
    def __init__(self,wheels,seats,doors):
        self.wheels = wheels
        self.seats = seats
        self.doors = doors

    def horn(self):
        print("Honk Honk")

    def start(self):
        print("Started")

    def stop(self):
        print("Stoped")


class Car(Vehical):
    def __init__(self,wheels,seats,doors):
        Vehical.__init__(self,wheels,seats,doors)

    def display(self):
        print("It is a car")


class Bus(Vehical):
    def __init__(self,wheels,seats,doors):
        Vehical.__init__(self,wheels,seats,doors)

    def display(self):
        print("It is a Bus")


omini = Car(4,6,4)
bus1 = Bus(6,20,2)
omini.start()
bus1.start()


```
**Multiple Inheritance**
Multiple level inheritance enables one derived class to inherit properties from more than one base class.
```{python}
class Alpha:
    def SayHi(self):
        print("Hi inside Alpha class")


class Beta:
    def SayBye(self):
        print("Bye from Beta class")


class Gamma(Alpha,Beta):
    def display(self):
        print("Welcome to Gamma class")
        super().SayHi()
        super().SayBye()


a1 = Gamma()
a1.display()
```

## Polymorphism
Polymorphism defines the ability to take different forms. Polymorphism in Python allows us to define methods in the child class with the same name as defined in their parent class.


**Method Overriding**
```{python}
class A:
    def display(self):
        print("Inside A class")

class B(A):
    def display(self):
        print("Inside class B")

a1 = B()
a1.display()
``` 
Here child class inherited it's parent class method but it made changes to the method in child class by doing that we are overriding the parent class display method with the child class display method.


## Encapsulation
The process of wrapping up variables and methods into a single entity is known as Encapsulation.It acts as a protective shield that puts restrictions on accessing variables and methods directly.


Using double underscore (\_\_variablename) to make a variable private and single underscore (\_variablename) to make it protected.


Private variables can be accessed within the same class.
Protected variables can be accessed within the same class and sub classes.


Encapsulation has some benefits, some of which are :
+ Data Hiding
+ Flexibility
+ Reusability

```{python}
class Tech:
   
    def __init__(self, name, course):
        self.name = name
        # private variable
        self.__course = course
      
    def set__course(self,c):
        self.__course = c

    def get__course(self):
        return self.__course 


t1 = Tech('Raina', 'Python')
print("Name:",t1.name)

# accessing private variable __course using 
# _Tech__course name
print("Course:",t1._Tech__course)

#accessing private variable using setter and getter methods
t1.set__course('Java')
print("Course:",t1.get__course())
```

## Abstraction
Abstraction refers to hiding unnecessary details to focus on the whole product instead of parts of the project separately. It is a mechanism that represents the important features without including implementation details. Abstraction helps us in partitioning the program into many independent concepts so we may hide the irrelevant information in the code. It offers the greatest flexibility when using abstract data-type objects in different situations.
```{python}

from abc import ABC, abstractmethod

class Vehicle(ABC):

    @abstractmethod
    def start(self):
        pass

class Car(Vehicle):
    def start(self):
        print("Car has started")

nano = Car()
nano.start()
```

**References:**
[Python Documentation](https://docs.python.org/3/tutorial/classes.html)
[OOPs in python by freecodecamp](https://www.freecodecamp.org/news/object-oriented-programming-in-python/)