Marcell Ciszek Druzynski
← Back to blog posts

Abstraction in Programming

I would like to discuss programming abstraction and its prevalence in our daily lives as programmers. Each day, we interact with some form of abstraction when we write code, whether it's using a new language feature, an external API, or creating our own abstraction, such as building our own React hook.

This post will include:

Abstraction is a useful tool in computer science that aims to simplify complex systems to enhance comprehension and ease of use. This means that irrelevant implementation details of APIs or data are hidden from the user, allowing them to focus on the relevant information. To illustrate this concept, consider a coffee machine; when using it, you do not need to understand the intricacies of how it functions, as all that matters is that it produces coffee. The machine's inner workings are abstracted from the user to make the experience simpler and more enjoyable. Similarly, imagine a city where every task must be approved by the mayor, including hospitals, schools, and fire departments. This process would be cumbersome and time-consuming. A solution would be to group tasks into specific departments such as the fire department. This grouping and labeling process is an abstraction, allowing the mayor to delegate tasks to the fire department without needing to know all the details of how they will carry them out.

Layers of Abstraction

In order to effectively manage complex systems, it is crucial to break them down into manageable components with clearly defined tasks. Whether it's computer systems or human systems, abstractions serve as a valuable tool to help navigate the intricacy of such systems. However, the use of abstractions can result in multiple layers of complexity, which is not always desirable. Although abstractions are a common practice in programming, adding too many layers of abstraction can have its drawbacks. For instance, consider the scenario where the Mayor communicates with the head of the Fire Department, who then communicates with the heads of each of the four firehouses, and so on. At each stage, there is a layer of abstraction being added, treating each component as an abstract entity. This can lead to significant delays in communication as it moves down the chain of command, which is one disadvantage of using multiple layers of abstraction.

Programming abstractions

The organizational layers that the Mayor encounters are not limited to human systems, but are also prevalent in computer science. For instance, when companies such as Intel or AMD design computer chips that power electronic devices, they also release an abstraction layer consisting of a set of commands for controlling the chip. These commands, known as assembly or machine instructions, provide a highly detailed way of writing programs that the chip can execute. While it's possible to write programs in machine code, it's an arduous process, akin to the Mayor micromanaging every detail of rescuing a cat or fixing a water pipe by calling every employee. Instead, it's more practical to use a programming language that's better suited for application development and is easier to write and comprehend, such as Javascript. Numerous programming languages, including Javascript, operate via interpretation. This entails the Javascript program acting as an interpreter, which takes the code written in Javascript and determines the corresponding machine instructions required to execute that code.

interpreter vs compiled

When it comes to programming languages, there are two main types of language implementations: interpreted and compiled. An interpreter reads and executes code directly from the source code, line-by-line, as it encounters it. This means that errors are typically discovered at runtime, when the code is actually being executed. Interpreted languages are often easier to develop and test, since changes to the code can be seen immediately upon execution. Examples of interpreted languages include Python, Ruby, and JavaScript. In contrast, a compiler takes the entire source code and translates it into machine code that can be executed directly by the computer. The resulting binary code can be run repeatedly without the need for the original source code. Since the code is checked for errors at compile-time, it tends to be faster and more efficient than interpreted code. However, compiling can be a slower process than interpreting, and changes to the code may require recompiling. Examples of compiled languages include Rust, Java, and `Go`` In summary, the main difference between interpreted and compiled languages is in the way that the code is executed. Interpreted code is executed line-by-line, while compiled code is translated into machine code before execution. Both approaches have their strengths and weaknesses, and the choice of which to use depends on the specific needs of the project.

The Cost of Abstractions

The incorporation of multiple layers of abstraction does not necessarily result in decreased speed or efficiency of a system. Even if abstraction does lead to reduced speed or efficiency, it can still have value. It is important to keep in mind that the purpose of abstractions is to simplify the management of complexity. At times, there is a tradeoff between the time taken by humans to solve a problem and the speed at which a computer can execute a solution designed by humans.

Interfaces

An important aspect of an interface is that it can be utilized without knowledge of its inner workings. For instance, the mayor may not be aware of the process used to prepare the meal they are consuming for lunch, yet they can still place an order. The mayor has other responsibilities besides obtaining the fire department memos and therefore does not need to concern themselves with the precise procedures involved in obtaining them. The focus is solely on obtaining the desired outcome, and the means by which it is achieved or the implementation details are not significant. When using an external API, for instance, the crucial aspect is obtaining the necessary data rather than the technical specifics of how the API delivers the outcome.

Interfaces for abstractions outline their capabilities and limitations.

Interfaces serve as abstractions that simplify complexity by outlining how to engage with a process while concealing the intricacies of the actual process implementation.

Abstraction is a methodology employed to simplify complexity by rendering a system more user-friendly, while obscuring extraneous details of its actual operations.

Abstraction in code

There are many different examples how we can demonstrate attractions but a common way that you learn in school is to give examples from the OOP(object oriented programming) paradigm.

A example could like like this:

1public interface Person {
2 void greet();
3 void birthday();
4}
5
6public class PersonImpl implements Person {
7
8 private final String name;
9 private int age;
10
11 public PersonImpl(String name, int age) {
12 this.name = name;
13 this.age = age;
14 }
15
16 @Override
17 public void greet() {
18 System.out.println("Hello My name is " + name + " and I am " + age + " years old!");
19 }
20
21 @Override
22 public void birthday() {
23 age++;
24 System.out.println("My age is now " + age);
25 }
26}
1public interface Person {
2 void greet();
3 void birthday();
4}
5
6public class PersonImpl implements Person {
7
8 private final String name;
9 private int age;
10
11 public PersonImpl(String name, int age) {
12 this.name = name;
13 this.age = age;
14 }
15
16 @Override
17 public void greet() {
18 System.out.println("Hello My name is " + name + " and I am " + age + " years old!");
19 }
20
21 @Override
22 public void birthday() {
23 age++;
24 System.out.println("My age is now " + age);
25 }
26}

Here, the interface defines the behavior of our class. We do not require knowledge of the object's variables or any additional details; we solely have access to a straightforward API provided by the interface, which fulfills our requirements. In this instance, we only necessitate calling the greet() and birthday() methods, and do not require any further information about the object itself.

Summary

Programming abstraction involves simplifying intricate systems by focusing on essential features and concealing unnecessary details that are not needed. This technique includes creating abstract models or representations of real-world objects or systems in code that can be used to construct more complex software. Abstraction offers programmers the ability to develop reusable code and modify complex systems, making it simpler to maintain, update, and debug software over time. By using abstraction, developers can conceal implementation details and reduce cognitive load, allowing them to focus solely on what they need to use and know. However, there are drawbacks to using abstraction. As we add more layers, the code can become increasingly mysterious, which isn't always desirable because developers want to have control over what they build and use. Another disadvantage of abstraction is that it can come at the expense of performance, as additional layers of abstraction can result in computational overhead. This is concerning in systems with tight performance requirements, such as real-time applications or high-throughput data processing.

Resources