[Python OOP] 4. Encapsulation and Abstraction

05 Nov 2023 By Code Bricks

4. Encapsulation and Abstraction

This section will delve into more advanced topics of encapsulation and abstraction, two core principles of object-oriented programming that allow us to hide the internal state of an object and expose only what is necessary.

4.1 Advanced Encapsulation

Encapsulation is the mechanism that restricts access to an object’s components, which is a fundamental principle to protect the object’s integrity by preventing outsiders from setting the internal data of the component to an invalid or inconsistent state.

4.1.1 Name Mangling

Name mangling is a technique used in Python to prevent subclass from accidentally overriding the private attributes and methods of a superclass.

class Account:
    def __init__(self, balance):
        self.__balance = balance  # Name mangling

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount  # Accessing the mangled name

    def get_balance(self):
        return self.__balance

acc = Account(1000)
print(acc.get_balance())  # Output: 1000
print(acc.__balance)      # AttributeError: 'Account' object has no attribute '__balance'

4.1.2 Using @property for Getter and Setter

Python doesn’t have built-in support for private attributes, but we can achieve a similar effect using @property decorators to create managed attributes, also known as “getters” and “setters”.

class Product:
    def __init__(self, price):
        self.__price = price

    @property
    def price(self):
        return self.__price

    @price.setter
    def price(self, value):
        if value < 0:
            raise ValueError("Price cannot be negative.")
        self.__price = value

product = Product(50)
print(product.price)  # Output: 50
product.price = 30
print(product.price)  # Output: 30
# product.price = -10  # ValueError: Price cannot be negative.

4.2 Implementing Abstraction

Abstraction is an extension of encapsulation. It involves the process of handling complexity by hiding unnecessary details from the user.

4.2.1 Abstract Classes

An abstract class can be considered as a blueprint for other classes. It allows you to create a set of methods that must be created within any child classes built from the abstract class.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

# This will raise an error if instantiated or if the subclass does not implement the abstract methods
# shape = Shape()

4.2.2 Abstract Methods

Abstract methods are those declared in the abstract class, which have no implementation. Subclasses are expected to implement these methods.

class Rectangle(Shape):
    def __init__(self, width, height):
        self.__width = width
        self.__height = height

    def area(self):
        return self.__width * self.__height

    def perimeter(self):
        return 2 * (self.__width + self.__height)

rectangle = Rectangle(2, 3)
print(rectangle.area())       # Output: 6
print(rectangle.perimeter())  # Output: 10

Note that we cannot instantiate an abstract class that has abstract methods. This encourages a child class to implement the abstract methods, thus adhering to the abstraction principle.