Abstraction is one of the key concepts of object-oriented programming (OOP) languages. Its main goal is to handle complexity by hiding unnecessary details from the user. That enables the user to implement more complex logic on top of the provided abstraction without understanding or even thinking about all the hidden complexity.
That’s a very generic concept that’s not limited to object-oriented programming. You can find it everywhere in the real world.
Abstraction in the real world
I’m a coffee addict. So, when I wake up in the morning, I go into my kitchen, switch on the coffee machine and make coffee. Sounds familiar?
Making coffee with a coffee machine is a good example of abstraction.
You need to know how to use your coffee machine to make coffee. You need to provide water and coffee beans, switch it on and select the kind of coffee you want to get.
The thing you don’t need to know is how the coffee machine is working internally to brew a fresh cup of delicious coffee. You don’t need to know the ideal temperature of the water or the amount of ground coffee you need to use.
Someone else worried about that and created a coffee machine that now acts as an abstraction and hides all these details. You just interact with a simple interface that doesn’t require any knowledge about the internal implementation.
You can use the same concept in object-oriented programming languages like Java.
Abstraction in OOP
Objects in an OOP language provide an abstraction that hides the internal implementation details. Similar to the coffee machine in your kitchen, you just need to know which methods of the object are available to call and which input parameters are needed to trigger a specific operation. But you don’t need to understand how this method is implemented and which kinds of actions it has to perform to create the expected result.
Different Types of Abstraction
There are primarily two types of abstraction implemented in OOPs. One is data abstraction which pertains to abstracting data entities. The second one is process abstraction which hides the underlying implementation of a process. Let’s take a quick peek into both of these.
Data Abstraction
Data abstraction is the simplest form of abstraction. When working with OOPS, you primarily work on manipulating and dealing with complex objects. This object represents some data but the underlying characteristics or structure of that data is actually hidden from you. Let’s go back to our example of making coffee.
Let’s say that I need a special hazelnut coffee this time. Luckily, there’s a new type of coffee powder or processed coffee beans that already have hazelnut in it. So I can directly add the hazelnut coffee beans and the coffee machine treats it as just any other regular coffee bean. In this case, the hazelnut coffee bean itself is an abstraction of the original data, the raw coffee beans. I can use the hazelnut coffee beans directly without worrying about how the original coffee beans were made to add the hazelnut flavour to it.
Therefore, data abstraction refers to hiding the original data entity via a data structure that can internally work through the hidden data entities. As programmers, we don’t need to know what the underlying entity is, how it looks etc.
Process Abstraction
Where data abstraction works with data, process abstraction does the same job but with processes. In process abstraction, the underlying implementation details of a process are hidden. We work with abstracted processes that under the hood use hidden processes to execute an action.
Circling back to our coffee example, let’s say our coffee machine has a function to internally clean the entire empty machine for us. This is a process that we may want to do every once a week or two so that our coffee machine stays clean. We press a button on the machine which sends it a command to internally clean it. Under the hood, there is a lot that will happen now. The coffee machine will need to clean the piston, the outlets or nozzles from which it pours the coffee, and the container for the beans, and then finally rinse out the water and dry out the system.
A single process of cleaning the coffee machine was known to us, but internally it implements multiple other processes that were actually abstracted from us. This is process abstraction in a nutshell.
Well, this process abstraction example really got me thinking of a very futuristic coffee machine!
Use abstraction to implement a coffee machine
Modern coffee machines have become pretty complex. Depending on your choice of coffee, they decide which of the available coffee beans to use and how to grind them. They also use the right amount of water and heat it to the required temperature to brew a huge cup of filter coffee or a small and strong espresso.
Implementing the CoffeeMachine abstraction
Using the concept of abstraction, you can hide all these decisions and processing steps within your CoffeeMachine class. If you want to keep it as simple as possible, you just need a constructor method that takes a Map of CoffeeBean objects to create a new CoffeeMachine object and a brewCoffee method that expects your CoffeeSelection and returns a Coffee object.
You can clone the source of the example project at https://github.com/thjanssen/Stackify-OopAbstraction.
CoffeeSelection is a simple enum providing a set of predefined values for the different kinds of coffees.
import org.thoughts.on.java.coffee.CoffeeException; import java.utils.Map; public class CoffeeMachine { private Map<CoffeeSelection, CoffeeBean> beans; public CoffeeMachine(Map<CoffeeSelection, CoffeeBean> beans) { this.beans = beans } public Coffee brewCoffee(CoffeeSelection selection) throws CoffeeException { Coffee coffee = new Coffee(); System.out.println(“Making coffee ...”); return coffee; } }
public enum CoffeeSelection {
FILTER_COFFEE, ESPRESSO, CAPPUCCINO;
}
And the classes CoffeeBean and Coffee are simple POJOs (plain old Java objects) that only store a set of attributes without providing any logic.
public class CoffeeBean {
private String name;
private double quantity;
public CoffeeBean(String name, double quantity) {
this.name = name;
this.quantity;
}
}
public class Coffee {
private CoffeeSelection selection;
private double quantity;
public Coffee(CoffeeSelection, double quantity) {
this.selection = selection;
this. quantity = quantity;
}
}
Using the CoffeeMachine abstraction
Using the CoffeeMachine class is almost as easy as making your morning coffee. You just need to prepare a Map of the available CoffeeBeans. After that, instantiate a new CoffeeMachine object. Finally, call the brewCoffee method with your preferred CoffeeSelection.
import org.thoughts.on.java.coffee.CoffeeException;
import java.util.HashMap;
import java.util.Map;
public class CoffeeApp {
public static void main(String[] args) {
// create a Map of available coffee beans
Map<CoffeeSelection, CoffeeBean> beans = new HashMap<CoffeeSelection, CoffeeBean>();
beans.put(CoffeeSelection.ESPRESSO,
new CoffeeBean("My favorite espresso bean", 1000));
beans.put(CoffeeSelection.FILTER_COFFEE,
new CoffeeBean("My favorite filter coffee bean", 1000));
// get a new CoffeeMachine object
CoffeeMachine machine = new CoffeeMachine(beans);
// brew a fresh coffee
try {
Coffee espresso = machine.brewCoffee(CoffeeSelection.ESPRESSO);
} catch(CoffeeException e) {
e.printStackTrace();
}
} // end main
} // end CoffeeApp
You can see in this example that the abstraction provided by the CoffeeMachine class hides all the details of the brewing process. That makes it easy to use and allows each developer to focus on a specific class.
If you implement the CoffeeMachine, you don’t need to worry about any external tasks, like providing cups, accepting orders or serving the coffee. Someone else will work on that. Your job is to create a CoffeeMachine that makes good coffee.
And if you implement a client that uses the CoffeeMachine, you don’t need to know anything about its internal processes. Someone else already implemented it so that you can rely on its abstraction to use it within your application or system.
That makes the implementation of a complex application a lot easier. And this concept is not limited to the public methods of your class. Each system, component, class, and method provides a different level of abstraction. You can use that on all levels of your system to implement software that’s highly reusable and easy to understand.