Abstraction simplified: real-world example every developer should understand
Demystifying Abstraction in programming: beyond interfaces and classes
Abstraction in programming might seem straightforward - something every developer encounters and quickly masters. For some, it might just mean slapping an interface or an abstract class onto their code and calling it a day.
But have you ever stopped and thought about what abstraction means?
Why do we rely on it so heavily in software design?
What real-world benefits does it bring to our code and our systems?
In this post, I'll explore these questions and what creating meaningful abstraction really means.
In simple terms: what is Abstraction?
This is a fundamental programming concept that allows us to manage complexity by focusing on the essential features of an object or system.
It's like designing a car dashboard —you interact with the steering wheel, pedals, and buttons without the need to understand the engine's mechanics.
In software development, abstraction helps us simplify code. It hides implementation details and exposes only what's needed to perform a task.
This makes our programs easier to read, maintain and expand, and promotes reusability and modularity.
The real benefit behind an Abstraction
The essence of abstraction is its ability to hide the intricate details of implementation. If you're interacting with parts of the code that are responsible for different things than the one you're working on now, you don't want to focus on how those parts work.
Let’s use a car analogy to explore abstraction.
Imagine you’re writing code for a car’s steering system, and you want to slow the car down. You don’t want to worry about the specifics - whether it’s engaging the brakes, reducing engine power, or some other mechanism.
Ideally, you’d simply call a method like slowDown() and specify how much to reduce the speed. The details of how it happens are abstracted away, letting you focus on what needs to be done, not how it’s achieved.
From the theory to the practice
There are lots of theories about abstraction. But let's look at a real example from the application I've been working with.
Reading data from files and storing it in a database is a common operation in many applications. Imagine allowing users to upload data in various formats while ensuring that this flexibility doesn’t complicate our import process.
To achieve this, we can treat reading data and storing it in the database as two independent steps.
Here’s what a basic version of our importer might look like without any abstraction:
class Importer
{
public function import(string $fileName)
{
if (substr($fileName, -4) == '.csv') {
$fileData = $this->readCSVFile($fileName);
}
// import action
}
public function readCSVFile(string $fileName)
{
$rows = [];
if (($handle = fopen("test.csv", "r")) !== false) {
while (($data = fgetcsv($handle, 1000, ",")) !== false) {
$rows[] = $data;
}
}
fclose($handle);
return $rows;
}
public function readXLSFile(string $fileName)
{
// reading implementation
}
}
In its current state, the implementation violates the first principle of SOLID. Moreover, the Importer class is overly focused on file reading - a detail that should be abstracted away.
When writing code to process and handle data, developers shouldn’t need to concern themselves with the specifics of how the data is being read.
Let's have a look at what our code would look like after the addition of an abstraction.
interface FileReader {
public function getDataFromFile();
}
Abstraction allows us to simplify complex systems by focusing on essential features while hiding unnecessary details. When creating an abstraction for a file reader, we define a general interface that every specific file reader implementation must follow.
This way, when we encounter a parameter of type FileReader, we can be confident that it can provide data from a file, without needing to know the specifics of the file format or how the reading is implemented.
class CSVReader implements FileReader
{
public function getDataFromFile()
{
// reading CSV file implementation
}
}
class XLSReader implements FileReader
{
public function getDataFromFile()
{
// reading XLS file implementation
}
}
After the addition of an abstraction, our importer doesn't have to know what kind of file we'll be reading the data from, because that's not something we have to worry about.
class Importer
{
public function import(FileReader $fileReader)
{
$fileData = $fileReader->getDataFromFile();
// import action
}
}
Now whatever we change in any of our file readers in the future, it will not require touching an Importer class.
This is one of the beauties of the abstraction layer, you can easily add new file readers and Importer will not even notice.
Conclusion
I hope you now have a clear understanding of what abstraction looks like in programming. You'll know how to use it effectively in your code.
But is abstraction something that should be used in every project? In my opinion, it should not. As a developer, it's important to think carefully about the tools you use. Abstraction is just one of many. The key is to know when it makes sense to use it, and when it can make things too complicated.