Pillars of OOPS (in python)
Tags: python, programming, oop
Published: 2021-07-02 12:33:51

> The scope of this article is to help you understand the concepts of oops quickly and not an in-depth tutorial on coding with oops.



Object-Oriented Programming system is also known as OOPS is one of the hot topics for programming interviews. I have been asked questions about this concept many times(direct and indirect questions) in the interview, and to be true, I was unable to answer the questions most of the time because I never had a clear understanding of it. But when I tried to look closely towards its advantages and try to understand them with my favorite programming language, I felt like I was an improved version of myself while writing the code and I hope it helps you improve your skills and knowledge as well. 

Let's start to understanding OOPS



There are four pillars

- Encapsulation
- Inheritance
- Polymorphism
- Abstraction

Let's Have a look at it each of them

## Encapsulation 

> encapsulation (the action of enclosing something in or as if in a capsule.)

Now, If you understood the word's meaning then you can relate the same with your code, lets look at the example.

```python
class Cat:

    def __init__(self):
        self.sound = "meow"

    def speak(self):
        print("Cat says: {}".format(self.sound))


c = Cat()
c.speak()

# change the price
c.sound = "bow-wow"
c.speak()


#OUTPUT

Cat says: meow
Cat says: bow-wow
```

In the above example, the class cat has a variable sound that is set to the default value in the constructor, and later we are also changing the value of this variable i.e. `c.sound = "bow-wow"`, Now that you know the meaning of encapsulation as per above definition let's consider class as a capsule, do you think this code follows encapsulation? Well, it doesn't.

To avoid this we can declare the private variables, in Python, we denote private attributes using underscore as the prefix i.e single _ or double __.


```python
class Cat:

    def __init__(self):
        self.__sound = "meow"

    def speak(self):
        print("Cat says: {}".format(self.__sound))


c = Cat()
c.speak()

# change the price
c.sound = "bow-wow"
c.speak()


#OUTPUT

Cat says: meow
Cat says: meow
```

Now, it has been encapsulated. Well, this is the encapsulation. 
Hence Some practical examples of this are

-  A bank application forbids (restrict) a client to change an Account's balance.
- It is also used to hide the data - let's say you log into your email account, there is a lot of process in the backend, that you have no control over. So your password, would probably be retrieved in an encrypted form, verified and only then you are given access. You do not have any control, over how the password is verified, and this keeps it safe from misuse. (source: [Quora](https://www.quora.com/What-are-some-real-world-example-of-encapsulation-and-abstraction-and-how-are-they-different-yet-related))

## Inheritance

> Inherit (to receive a quality, characteristic, etc. from your parents or family.)

As you see the definition above, This allows us to define a class that inherits methods, properties from another class. Let's say you need a new class with little or no modification then you apply this concept.

The base class is called the parent class and the derived class is known as a child. It's Obvious, I know :)

Let us see an example

```python
class Family:
    def __init__(self, family_name, number_of_members, country):
        self.family_name = family_name
        self.number_of_members = number_of_members
        self.country=country

    
    def member_says(self):
        print(f"Hey, I am from {self.family_name} family and there are {self.number_of_members} members in family")

        
class Family_detailed:
    def __init__(self, family_name, number_of_members, country):
        self.family_name = family_name
        self.number_of_members = number_of_members
        self.country=country
    
    def member_says(self):
        print(f"Hey, I am from {self.family_name} family and there are {self.number_of_members} members in family")
    
    def which_country(self):
        print(f"The {self.family_name} family has roots from {self.country}" )
        

a = Family("Rodrigues",5,"Peru")
b = Family_detailed("Bezos",15,"United States of America")

a.member_says()
b.member_says()
b.which_country()

#Output

Hey, I am from the Rodrigues family and there are 5 members in the family
Hey, I am from the Bezos family and there are 15 members in the family
The Bezos family has roots in the United States of America
```

The purpose of the first class is to display family names and the number of members but the second class wants to display the same information along with the country. As you see there is a lot of common code here, let's use inheritance to make it simpler.

```python
class Family:
    def __init__(self, family_name, number_of_members, country):
        self.family_name = family_name
        self.number_of_members = number_of_members
        self.country=country
        
    def member_says(self):
        print(f"Hey, I am from {self.family_name} family and there are {self.number_of_members} members in family")

        
class Family_detailed(Family):
    
    def which_country(self):
        print(f"The {self.family_name} family has roots from {self.country}" )
        

a = Family("Rodrigues",5,"Peru")
b = Family_detailed("Bezos",15,"United States of America")

a.member_says()
b.member_says()
b.which_country()

#Output

Hey, I am from the Rodrigues family and there are 5 members in the family
Hey, I am from the Bezos family and there are 15 members in the family
The Bezos family has roots in the United States of America
```

Wow, as you see the code has reduced significantly. So, this was the inheritance, let's move to the next pillar.


## Abstraction

Abstraction (A general idea rather than one relating to a particular object, person, or situation.)

To understand it better, let's take an example of a car, people don't see the car as thousands of individual parts, instead, they see it as a well-defined object with unique behavior. They don't need to understand the complexity of those parts and how to collaborate with each other. The same goes with a vending machine, you just want to put money in and get your favorite snack and there is no need of understanding how the system works.

in simple terms, Abstraction focuses on hiding the internal implementations of a process or method from the user. In this way, the user knows what they are doing but not how the work is being done.

This is a little different than the other pillars. Python does not grant abstract classes by default. Instead, Python comes with a module that fits the base for Abstract Base classes(ABC) and that module name is ABC. A method becomes abstract when decorated with the keyword @abstractmethod

Let's take a simple example

```python
from abc import ABC, abstractmethod
class Company(ABC):
 
    def work(self):
        pass
 
class Manager(Company):
 
    def work(self):
        print("I assign work to and manage team")
 
class Employee(Company):
 
    def work(self):
        print("I complete the work assigned to me")
         
# Driver code
R = Manager()
R.work()
 
K = Employee()
K.work()
```



## Polymorphism

polymorphism (the condition of occurring in several different forms)

One of the awesome examples of understanding this will be Ben 10. He's one person but he can serve the world in multiple forms.

![Ben 10 with his characters](https://deadline.com/wp-content/uploads/2019/01/ben-10-2.jpg)

Back to programming, it refers to the use of a single method/operator to represent different types in different scenarios.

Python can use multiple classes in the same way, using Polymorphism. To serve this purpose, we can create a loop that iterates through a tuple of objects. And this will allow us to call methods without looking at the class to which the object is pointing. 


```python
class Class1(): 
    def pt(self): 
        print("This function determines class 1") 
   
class Class2(): 
    def pt(self): 
        print("This function determines class 2.") 
   
obj1 = Class1() 
obj2 = Class2() 
for type in (obj1, obj2): # creating a loop to iterate through the obj1, obj2
    type.pt() 


#Output
This function determines class 1
This function determines class 2.
```
similarly talking about the operators, let's look at the `+` operator.

```python
> print(1+2)
3
> print ("a"+"b")
> ab
```

As you see, the `+` operator behaves different way with different datatypes.



That's all for this post folks.
Don't forget to ❤ or 📑 this article if you enjoyed it.

Have a great day.
Happy Coding!!