Objects and Object Oriented Programming
Tags: oop, pythonoops, object, class
Published: 2023-02-16 08:40:49

Nowadays the modern software development resides around objects and its oriented programming for a good reason. As it gives a lot of benefits over the old paradigm of procedural programming.
* Python is an object oriented programming language
* Object orient programming is a way to design the program using classes and objects.
* The main concepts of OOPs is to bind the data and the functions that work on the data together as a single unit os that no other part of the code can access this data
* The four concepts that contructs the OOP are data abstraction, polymorphism, encapsulation and inheritance

## Main concepts of OOP

### Class

* Class consists of a collection of objects
* Classes are known as the blueprint of objects
* Classes have there own attributes and methods that has the accessibility only by the objects of that class
* We can initialize the attributes of a Class using init method

```py
class Mountblue:
    def __init__(self, name, company):
        self.name = name
        self.company = company
    
    def some_method_to_work(some arguments):
        # Statements to execute
        # return statements
```

### Object

* Objects are the instance of a class
* Object are just like real world entities like a car, boy, girl, etc
* An object consists of methods and attributes
* Object of a class consist of self parameter which represents the attributes of its class.
* We can use any parameters in any methods belong to the same class.
  
```py
class Car:
    def __init__(self, brand, model, color):
        self.brand = brand
        self.model = model
        self.color = color
    
    def show_props(self):
        print(self.brand, self.model)
        print(self.color)

s = Car("Tata", "Nano", "Blue")
s.show_props()

## output: 
Tata Nano
Blue
```

## Data Abstraction

* Data Abstraction or data hiding is to avoid details of an object which are not used in the current task
* For example, if we are driving a car, we do not need to know the mechanism of how the car works when we push gear, acceleration or brakes.
* For Data hiding: We use double underscore("__") before the attributes names and those attributes will not be directly visible outside.
  
```sh
class MyClass:
    __thisvariablehidden = 0
    def adder(self, increment):
    self.__thisvariablehidden += increment
    print(self.__thisvariablehidden)

object1 = MyClass()
object1.add(200)
object1.add(72)

print(object1.__thisvariablehidden)
# output: 200
#          72

error:
MyClass instance has 
no attribute '__thisvariablehidden'
```

## Encapsulation

* OOP allows us to encapsulate data and behaviour within objects.
* This means that we can hide implementation details and only the interface is visible for the other parts of the program
* It helps us for code maintainability and updation the code over time
* Protected members(single underscore"_") are those classes which cannot be accessed outside the class but can be accessed from within the class
* Private members(double underscore"__") are the class members that cannot be accessed outside the class as well as inside of any base class. Completely private protected file

```py
class Account:
    def __init__(self, balance):
        self.__balance = balance # private attribute
    
    def deposit(self, amount):
        self.__balance += amount

    def get_balance(self):
        return self.__balance

# The __balance attribute is marked as private by using double underscore
# By using this the balance is secured 
a = BankAccount(50)
a.deposit(50)
print(a.get_balance())
# 100
```

## Polymorphism 

* Polymorphism means one type having many forms
* It allows us to write code that can work with objects of different classes, as long as they have common interface
* With polymorphism we can can the code more flexible and reusable

```py
def add(x, y, z = 0):
    return z + y + z

print(add(4, 5))
print(add(4, 5, 6))
# output : 9
#          15
```

### Method Overriding 
* It allows a subclass or child class to provide a specific kind of implementation of a method that is already provided by one of its super-classes or parent classes.
* When a method in a subclass has the same name, same parameters or signature and same return type as a method in its super-class, then the method in the subclass is said to override the method in the super-class.

```py
class Parent():
    def __init__(self):
        self.value = "Inside Parent"
    
    def show(self):
        print(self.value)

class Child(Parent):
    def __init__(self):
        self.value = "Inside Child"
    def show(self):
        print(self.value)

obj1 = Parent()
obj2 = Child()

obj1.show()
obj2.show()
# Output: Inside Parent
#         Inside Child  
```

### Method Overloading

* Two or more methods have the same name but different numbers of parameters or different types of parameters, or both.
* The problem with method overloading in python is that we may overload the methods but can only use the latest defined method.
  
```py
def product(a, b):
    d = a * b
    print(d)

def product(a, b, c):
    d = a * b * c
    print(d)

product(3, 4, 2)
# output : 24 
```

## Inheritance

* Inheritance is the capability of one class to derive or inherit the properties from parent class.
* This can be useful when we have several classes that share common properties or behavior
* It provides reusability of a code and allows us to add more features to a class without modifying it.

### Types of Inheritance

### 1. Single Inheritance

* New class is called the derived class or subclass, and the existing class is called the base class or superclass.
* The derived class inherits al the properties of the base class 

```py
class Animal:
    def __init__(self, name, sound):
        self.name = name
        self.sound = sound
    
    def make_sound(self):
        print(f"{self.name} says {self.sound}!")

class Dog(Animal):
    def __init__(self, name):
        super().__init__(name, "Woof")

dog = Dog("Tommy")

dog.make_sound()
# output : "Tommy says Woof!"
```

### 2. Multi-level Inheritance

* In this inheritance a derived class is created based on another derived class, which is based on a base class.

```py
class Animal:
    def __init__(self, name, sound):
        self.name = name
        self.sound = sound
    
    def make_sound(self):
        print(f"{self.name} says {self.sound}!")4

class DomesticAnimal(Animal):
    def __init__(self, name, sound, breed):
        super().__init__(name, sound)
        self.breed = breed
    
    def show_breed(self):
        print(f"{self.name} is a {self.breed}.")

class Dog(DomesticAnimal):
    def __init__(self, name, breed):
        super().__init__(name, 'Woof', breed)

dog = Dog("Tommy", "German Spitz")

dog.make_sound()
# output : "Tommy says Woof!"
dog.show_breed()
# output : "Tommy is a German Spitz"
```

## References :

* [Python Classes](https://docs.python.org/3/tutorial/classes.html)
* [Python OOPs, GeeksforGeeks](https://www.geeksforgeeks.org/python-oops-concepts/)
* [Python OOPs, javatpoint](https://www.javatpoint.com/python-oops-concepts)