Abstraction
Abstraction
Abstraction hides implementation details behind simple interfaces, helping you build modular, maintainable code.
By defining abstract classes or interfaces, you enforce a contract that all subclasses must fulfil, yet remain free to vary their internal workings.
Overview: Abstraction
Abstraction separates what an object does from how it does it. You work with high-level operations without needing to know underlying complexity.
Abstract classes or interfaces define method signatures but defer implementation to concrete subclasses, ensuring a consistent API.
Deep Dive: Abstraction
Why Abstraction Matters
Abstraction is the skill of focusing on the what and hiding the unnecessary how. It removes distracting detail so we can reason about a problem at the right level. In everyday life, you press a toaster’s lever and choose “medium”-you don’t manage coil temperature or timers. A car’s brake pedal gives you “slow down” without exposing hydraulics. The same idea powers good software design.
In code, a well-named function like send_email(to, subject, body) hides SMTP setup, retries, and logging.
A library call such as sort(students) hides algorithm details; a database query
SELECT name FROM students WHERE id = ? hides indexes and disk blocks. These abstractions let you build
features faster and change the internals later without breaking how it is called.
- Clarity: show only what matters at this level, as there is less code to scan; easier to read and explain (e.g. call
send_email(...)instead of implementing SMTP everywhere). - Safety: keep low-level details hidden so misuse is harder (e.g. don’t expose raw file handles or raw SQL; provide a checked helper or data-access method).
- Flexibility: let one interface back different implementations without changing callers (e.g.
EmailSender→FakeEmailSenderin tests,SmtpEmailSenderin production). - Testability: small, clearly defined units are simpler to isolate and verify.
- Collaboration: teams agree on interfaces early and develop components in parallel.
Rule of thumb: name your abstractions in the language of the problem (domain terms), expose only what users need, and keep the messy details tucked safely behind the interface.
Abstract Classes
An abstract class is a blueprint for a family of related classes.
An abstract class promises method signatures (abstract methods) with no implementation: the implementation is required by the subclass.
It can also define shared data and behaviour.
You cannot create an object directly from an abstract class; instead you create objects from
its subclasses that complete the missing pieces. (Java uses the abstract keyword; Python uses
abc.ABC and @abstractmethod to mark an abstract base class.)
- Purpose: capture what all variants have in common (names, fields, shared logic) and specify a small set of behaviours that each variant must implement. This gives you a single, consistent API across different implementations.
- Prevents misuse: by making the base type non-instantiable, you avoid half-baked objects that lack required behaviour. Subclasses must supply the missing methods before they can be used.
- Removes duplication: shared code (validation, logging, caching, helper methods) lives once in the abstract class and is inherited by every subclass.
- Enables substitution: code can depend on the abstract type (e.g. Payment or Sensor) and work with any subclass (CardPayment, MobilePayment; TempSensor, MockSensor) without changes-useful for swapping real and test implementations.
- Supports parallel development: teams agree the abstract API early, build different subclasses in parallel, and plug them in later with fewer merge conflicts.
When to use: If you only need to promise method signatures with no implementation, or variants share state and some default behaviour.
Abstract Class Implementation
Payment Processing
Suppose you need to process payments via different gateways. Define a common contract in an abstract base class, and let each gateway (e.g. Stripe, PayPal) implement its own details.
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process(self, amount: float) -> None:
# No implementation; must be implemented by sub-class
class StripeProcessor(PaymentProcessor):
def process(self, amount: float) -> None:
# call Stripe API
print(f"Processed £{amount} via Stripe")
class PaypalProcessor(PaymentProcessor):
def process(self, amount: float) -> None:
# call PayPal API
print(f"Processed £{amount} via PayPal")
# Demo
if __name__ == "__main__":
for proc in (StripeProcessor(), PaypalProcessor()):
proc.process(29.99)
abstract class PaymentProcessor {
public abstract void process(double amount);
// No implementation; must be implemented by sub-class
}
class StripeProcessor extends PaymentProcessor {
@Override
public void process(double amount) {
// call Stripe API
System.out.println("Processed £" + amount + " via Stripe");
}
}
class PaypalProcessor extends PaymentProcessor {
@Override
public void process(double amount) {
// call PayPal API
System.out.println("Processed £" + amount + " via PayPal");
}
}
public class PaymentDemo {
public static void main(String[] args) {
PaymentProcessor p1 = new StripeProcessor();
PaymentProcessor p2 = new PaypalProcessor();
p1.process(29.99);
p2.process(42.00);
}
}
Shape → Circle, Rectangle
An abstract Shape declares area(). Subclasses provide their own calculations.
# Abstract base class: defines the contract for all shapes
from abc import ABC, abstractmethod
class Shape(ABC):
# Abstract method: subclasses must implement their own area calculation
@abstractmethod
def area(self) -> float:
...
# Inheritance: Circle extends Shape and provides concrete implementation
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
# Overriding the abstract area() method to calculate circle area
def area(self) -> float:
return 3.1416 * (self.radius ** 2)
# Inheritance: Rectangle extends Shape and provides its own area logic
class Rectangle(Shape):
def __init__(self, w: float, h: float):
self.w, self.h = w, h
# Overriding the abstract area() method to calculate rectangle area
def area(self) -> float:
return self.w * self.h
# Polymorphism: treating different Shape subclasses uniformly
if __name__ == "__main__":
for shape in (Circle(5), Rectangle(4, 6)):
print(shape.area())
// Abstract class: defines the area() method signature
abstract class Shape {
public abstract double area();
}
// Inheritance: Circle extends Shape
class Circle extends Shape {
private double radius;
public Circle(double r) { this.radius = r; }
// Overriding area() to calculate circle area
@Override
public double area() { return Math.PI * radius * radius; }
}
// Inheritance: Rectangle extends Shape
class Rectangle extends Shape {
private double w, h;
public Rectangle(double w, double h) { this.w = w; this.h = h; }
// Overriding area() to calculate rectangle area
@Override
public double area() { return w * h; }
}
// Polymorphism: treat all shapes via the Shape reference
public class GeometryDemo {
public static void main(String[] args) {
Shape[] shapes = { new Circle(5), new Rectangle(4, 6) };
for (Shape s : shapes) {
System.out.println(s.area());
}
}
}
Key Takeaways
- Abstraction conceals complexity behind simple interfaces, making code modular.
- Abstract classes cannot be instantiated.
- Abstract classes create or promise abstract methods.
- Concrete subclasses must implement all abstract methods before they can be instantiated.
- Use abstraction to enforce a consistent API across diverse implementations.