Abstraction means hiding complexity and exposing only what callers need. The caller knows what an object can do, not how it does it. When you drive, you press the accelerator; you don't manage fuel injection or the transmission.
Abstraction vs encapsulation#
These two are easy to mix up.
| Encapsulation | Abstraction | |
|---|---|---|
| Hides | Data and internal state | Complexity and implementation |
| Focus | How data is protected | Which operations are available |
| Python tools | Private attributes, @property |
ABC, @abstractmethod |
| Question it answers | "How is the data protected?" | "What can this object do?" |
| ATM analogy | Your balance is hidden | Only deposit/withdraw buttons are shown |
In short: encapsulation says "you can't touch the data directly"; abstraction says "you don't need to know how it's done".
The problem it solves#
Without a contract, nothing stops someone from writing an incomplete class. You only find out when it crashes:
class CreditCard:
pass # forgot to implement pay()
c = CreditCard()
c.pay(100) # AttributeError at runtime
Abstraction lets you enforce a contract so the mistake surfaces as soon as someone tries to create the object.
Abstract base classes in Python#
Python's abc module lets you declare methods that every subclass must implement:
from abc import ABC, abstractmethod
class Payment(ABC): # ABC marks this as an abstract class
@abstractmethod
def pay(self, amount): # every concrete subclass must implement this
pass
Three cases to know:
p = Payment() # TypeError: can't instantiate abstract class
class CreditCard(Payment):
pass
c = CreditCard() # TypeError: CreditCard doesn't implement pay()
class CreditCard(Payment):
def pay(self, amount):
print(f"Paying {amount} via Credit Card")
c = CreditCard()
c.pay(100) # Paying 100 via Credit Card
Example: a payment system#
Each payment method hides its own processing behind one shared pay method:
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount):
pass
@abstractmethod
def refund(self, amount):
pass
class CreditCard(Payment):
def pay(self, amount):
print(f"Processing credit card payment of {amount}")
def refund(self, amount):
print(f"Refunding {amount} to credit card")
class PayPal(Payment):
def pay(self, amount):
print(f"Processing PayPal payment of {amount}")
def refund(self, amount):
print(f"Refunding {amount} to PayPal")
class Crypto(Payment):
def pay(self, amount):
print(f"Processing crypto payment of {amount}")
def refund(self, amount):
print(f"Refunding {amount} to crypto wallet")
# The caller only knows the Payment contract, not how each one works
for p in [CreditCard(), PayPal(), Crypto()]:
p.pay(100)
Processing credit card payment of 100
Processing PayPal payment of 100
Processing crypto payment of 100
Abstract classes can have concrete methods#
An abstract class can mix required methods with shared, ready-made ones:
class Payment(ABC):
@abstractmethod
def pay(self, amount):
pass
def print_receipt(self, amount): # inherited as-is
print(f"Receipt: Payment of {amount} processed")
class CreditCard(Payment):
def pay(self, amount):
print(f"Paying {amount} via Credit Card")
c = CreditCard()
c.pay(100)
c.print_receipt(100)
Example: shapes with abstraction and encapsulation#
This example combines an abstract contract with private, validated state:
import math
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Circle(Shape):
def __init__(self, color, radius):
self.__color = color # encapsulated
self.radius = radius
@property
def color(self):
return self.__color
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
class Square(Shape):
def __init__(self, color, size):
self.__color = color
self.size = size
@property
def color(self):
return self.__color
def area(self):
return self.size * self.size
def perimeter(self):
return 4 * self.size
c = Circle("Red", 5)
print(round(c.area(), 2), round(c.perimeter(), 2), c.color) # 78.54 31.42 Red
s = Square("Blue", 4)
print(s.area(), s.perimeter(), s.color) # 16 16 Blue
Where it shows up in LLD#
You almost always reach for abstraction when:
- Several classes share a common interface (
Payment,Vehicle,Notifier). - You want to enforce a contract across all implementations.
- You want polymorphism: treat different objects the same way through one type.
Key takeaways#
- Abstraction = hide the implementation, expose an interface.
- Use
ABCand@abstractmethodto enforce a contract in Python. - Abstract classes can't be instantiated; neither can subclasses that miss an abstract method.
- Abstract classes may also provide concrete, shared methods.
- Abstraction is what makes polymorphism useful.