Abstraction in Low-Level Design, exploring detail hiding, levels of abstraction, and how it differs from encapsulation.
In object-oriented system design, Abstraction is the process of modeling a complex real-world entity by focusing only on the properties and behaviors that are relevant to the current context, while filtering out (abstracting away) unnecessary details.
Since we cannot (and do not need to) model every single detail of a real-world entity in code, we build a simplified representation containing only what is relevant to our system.
This article covers how abstraction manages system complexity, provides concrete code examples, and clarifies the difference between abstraction and encapsulation.
Abstraction is about reducing complexity by filtering out irrelevant details. The same real-world entity can have completely different abstractions depending on the context of the system:
A Car in a Racing Game: The abstraction model includes topSpeed, acceleration, and steer(). It ignores price, VIN, and owner details.
A Car in a Dealership Inventory: The abstraction model includes price, VIN, and manufacturer. It ignores physics, speed, and steering mechanics.
Beyond modeling, abstraction is also used to hide the complex inner workings of a system behind a simplified interface.
For example, to drive a car, you only need to interact with the dashboard, steering wheel, and pedals. You do not need to understand the combustion engine or the transmission system under the hood. The pedals form the abstraction layer shielding you from that complexity.
In software, we achieve this by defining clean contracts (interfaces or abstract classes) and hiding the complex implementation details behind them.
[!NOTE]
Abstraction vs. Interface:Abstraction is a design goal (hiding complexity). An Interface is a language feature (a mechanism we use to achieve that goal). You can build abstractions using interfaces, abstract classes, or even simple public method signatures.
For instance, a concrete OrderProcessor class with a simple public method:
public class OrderProcessor { // Simple public method signature (the abstraction contract) public void process(Order order) { validate(order); chargeCard(order.getPaymentDetails()); updateInventory(order.getItems()); } // Complex implementation details are kept private and hidden private void validate(Order o) { /*...*/ } private void chargeCard(PaymentDetails p) { /*...*/ } private void updateInventory(List<Item> items) { /*...*/ }}
Here, the caller only depends on the simple process() abstraction, completely unaware of the underlying credit card charging or inventory logic.
Imagine a service that needs to save user data. The service should not care how or where the data is saved (e.g., in a SQL database, a file, or cloud storage). It should only care that a storage mechanism exists.
The client depends only on the abstraction, staying completely clean of database-specific logic:
public class UserService { private final UserStorage storage; // Depends on abstraction public UserService(UserStorage storage) { this.storage = storage; } public void registerUser(User user) { // Business logic... storage.save(user); // Simple, clean call }}
An Abstract Class provides abstraction at two different levels:
For the Client (Caller): It acts as a unified interface. The client doesn't know (or care) which methods are implemented by the base class and which ones are overridden by a subclass—they just use the high-level template.
For the Subclass (Implementer): It abstracts away the shared boilerplate (such as base constructors, common fields, and helper methods), so the subclass developer only has to focus on implementing the unique, subclass-specific logic.
Abstraction: Simplifies systems by hiding implementation details and exposing only what is necessary.
Decoupling: Depending on abstractions allows you to change the underlying implementation (e.g., swapping SQL storage for Cloud storage) without affecting the rest of the application.
Abstraction vs. Encapsulation: Abstraction hides complexity at the design level; encapsulation hides data and bundles it with behavior at the implementation level.