Object Oriented Programming
Published: 2023-02-16 07:40:37


- Solving a problem by creating objects is one of the most popular approaches in programming this is called object-oriented programming. The concept focuses on using reusable code that implements the dry principle.

- Python can be used as a declarative modular programming language as well as being used for imperative programming or full object-oriented programming. Declarative programming is a paradigm in which we focus on what we want to implement. Imperative programming is where we describe the exact steps of how to implement what we want.

## Class

- A class is a blueprint for how something should be defined it doesn't contain any data it is a template that is used to create instances as per the specifications defined in a template or a blueprint.

    ```python
    class Car()
        pass    
    ```

## Object

- An object of a class is an instance that is built from a class and that is why it is also called an instance of a class.

- An object is an instantiation of a class. When a class is defined, a template is defined and memory is allocated only after object instances. Objects of a given class can invoke the methods available to it without revealing the implementation details to the user which is often called abstraction and encapsulation.

    ```python
    # The first letter of the class name should always be a capital letter.
    class Car():
        pass

    c1 = Car() #c1 is the object of class car. 
    ```

## Distinguishing between class attributes and instance attributes

- Class attributes are defined as part of the class definition and their values are meant to be the same across all instances created from that class.
 
- The class attributes can be accessed using the class name or instance name, although it is recommended to use as class name to access these attributes. 

- The state or data of an object is provided by instance attributes.
    
    ```python
    class Car():
        pass

    if __name__ == "__main__":

        #Instance of a class
        car = Car()

        #Instance attributes
        car.color = 'blue'
        car.miles = 100
    ```

- We created an instance(car) of our Car class and then added two attributes to this instance: colour and miles. 
- The attributes added using this approach are instance attributes.


## Self

- Self is a reference to the instance that is being created. The use of self is to access the instance attributes and methods within the instance method, including the init method. 

- self is not a keyword and can be anything such as this, that, etc.

- **It has to be the first parameter of the instance methods.**

    ```python
    class Car():
        
        #class attributes
        c_mileage_units = "Mi"

        def __init__(self, color, miles):
            #Instance Variables
            self.color = color
            self.miles = miles

    if __name__ == "__main__":

        #Instance of a class
        car = Car("blue", 1000)
        

        #Instance attributes
        print(car.color)
        print(car.miles)
        print(car.c_mileage_units)
    ```

 
## Constructors:

- The constructor does nothing other than initialise the instance of a class.
- The purpose of having constructors in a class is to initialise or assign the values to the class or instance level attributes mainly instance attributes whenever an instance of a class is being created.

- In Python, the __init__ method is known as the constructor and is always executed when new instances are created.

- There are three types of constructors:
    - Default constructor 
    - Parameterized constructor 
    - Non-parameterized constructor

- When we do not include any constructor in a class, then the class will use an empty default constructor.
- A parameterized constructor can take one or more arguments and the state of the instance can be set as per the input arguments provided through the constructor method.
- A non-parameterized constructor does not take any arguments except a reference to the instance being created.

## Methods in a class

- There are three types of methods in a class
    - Instance method 
    - Class method 
    - Static method

### Instance methods
- They are associated with an instance and need an instance to be created first before executing them they accept the first attribute as a reference to the instance self and can read and update the state of the instance.

### Class methods
- These methods are declared with the @class method decorator.

- These methods don't need a class instance for execution.

- For this method, the class reference(cls) will be automatically sent as the first argument.

### Static methods

- These methods are declared with the @static method decorator.

- They don't have access to cls or self-objects. 

- Static methods are like utility functions that take certain arguments and provide the output based on the argument values.

- Static method works like regular functions that we define in modules but are available in the context of the class's namespace.    

```python
    class Car():
        
        #class attributes
        c_mileage_units = "Mi"


        #Constructor
        def __init__(self, color, miles):
            #Instance Variables
            self.color = color
            self.miles = miles

        def print_color(self):
            print(f"color of the car is {self.color}")

        @classmethod
        def print_units(cls):
            print(f"mileage units are {cls.c_mileage_units}") 
            print(f"Class name is {cls.__name__}")   

        @staticmethod
        def print_hello():
            print("Hello from a static method")

    if __name__ == "__main__":

        #Instance of a class
        car = Car("blue", 1000)
        

        #Instance attributes
        car.print_color()
        car.print_units()
        car.print_hello()

    # We can trigger the instance method using the class name, but we need to pass the instance variable as the first argument.
    # This again explains why we need the self-argument for each instance method.
```

## Advantages of Object-Oriented Programming

- Mostly we discuss abstraction, encapsulation, polymorphism and inheritance in the advantages of object-oriented programming.

- Abstraction Python is defined as hiding the implementation of logic from the client and using the particular application.

- Encapsulation in Python describes the content of bundling data and methods within a single unit example when you create a class it means you are implementing encapsulation.

- Class can inherit elements from another class. These elements include attributes and methods of the class from which we inherited under classes commonly known as parent classes. This is known as inheritance.

- Polymorphism is the ability of an instance to behave in multiple ways and a way to use the same method with the same name and the same arguments to behave differently following the class it belongs to. It can be implemented in two ways method overloading and method overriding

## References

To know more about it click on the below links:
- [Abstraction, Encapsulation, Inheritance, Polymorphism](https://www.scaler.com/topics/oops-concepts-in-python/)