Abstraction in OOPS: An Introduction With Examples in Java and Python
In this post we will develop an understanding of abstraction in object oriented programming and learn how to apply abstraction in Java and Python.
What is Abstraction (in Computer Science)?
Abstraction in everyday language refers to the practice of removing concrete details and dealing only with ideas that are relevant to the problem at hand.
In object-oriented programming, abstraction refers to the practice of exposing strictly necessary functionality to external objects while hiding implementation details. This enables callers of the code to make use of complex programming logic inside an object without the need to understand it. You only need to know what the program does, but not how it does it.
Think about how you are driving a car. You probably don’t know exactly how the engine works to power your vehicle. All you need is the knowledge that by pressing the gas pedal, your car will accelerate.
In a complex software system, you usually separate responsibilities into entities that only know enough about each other to use each other’s required functionality, but not how that functionality is implemented. For example, a payroll system in a company tasked with calculating employees’ payout doesn’t care about the job title or the skills of the employees. It only needs to know their salaries. Every type of employee has a salary. So instead of feeding every single type of employee with their respective skills to the payroll system, we abstract these details away by creating a person class that encompasses all employees and contains the salary information. This is also known as data abstraction.
Abstraction in Java
Abstraction Java is achieved through interfaces and abstract classes. As discussed in the post on polymorphism, interfaces are neither instantiated nor do they implement methods or variables. They just define a set of methods a conforming class needs to have. In other words, they guarantee to the caller that a conforming object has certain functionality while abstracting away the implementation details. The functions that interact with the conforming objects only need to know the interface. They don’t care about the concrete objects and how they implement the methods.
Abstraction in Java using an Interface
We could implement the person in our payroll system as an interface. The payroll system only wants a method to get the employee’s salary. Thus, we create a “Person” interface that has a method to return the salary.
public interface Person { double getSalary(); }
The payroll system takes this person interface to calculate the annual payout.
class PayrollSystem { public double calculateAnnualPayout(Person person) { double salary = person.getSalary(); double annualSalary = salary * 12; // in our simple economy there is a flat tax + social security of 30% double afterTaxSalary = annualSalary - (0.3 * annualSalary); return afterTaxSalary; } }
Up until now, we haven’t even defined a concrete person object. Nevertheless, the code compiles because all the payroll system needs is a method to retrieve the salary. Where it comes from, is irrelevant. This is abstraction at work.
We can define a concrete employee, let’s say a software engineer, and make her/him conform to the Person interface. The software engineer class has specific calculations related to software engineering salaries and a method to retrieve the programming languages he knows. All of this is irrelevant to the payroll system.
class SoftwareEngineer implements Person { //method required by Person interface public double getSalary(){ //calculations specific to compensation for software engineers goes here return 10000; } public String getProgrammingLanguages(){ return "Java, Python, C++"; } ... //more software engineer-specific methods }
Ultimately, we can pass the concrete software engineer to the payroll system’s “calculateAnnualPayout” method because our engineer conforms to the Person interface and is thus guaranteed to have a “getSalary” method.
Abstraction using the Java Abstract Class
An abstract class is a class that is used to define behavior but is not instantiated. So you cannot create an object using an abstract class. An abstract class can define abstract methods that specify the method name, arguments, and return type, but do not provide an implementation. Now you might say: This is exactly the behavior of an interface. And you are right. We can implement the person interface using an abstract class and abstract methods.
public abstract class Person { abstract double getSalary(); }
The compiler should accept this replacement without complaints and running it will give you the same result.
Java Abstract Class vs Interface
If the abstract class does the same thing as the interface, why do we need both in the first place? In addition to defining how a method should behave and contrary to an interface, an abstract class also allows you to selectively provide implementations for methods.
For example, you could implement a method in the Person class to get the person’s name as well as an instance variable “name”, and a constructor.
public abstract class Person { private String name; public Person(String name){ this.name = name; } public String getName(){ return this.name; } abstract double getSalary(); }
But remember, that you cannot actually implement the person class. So the concrete method implementations only make sense if they are used by extending subclasses. If you have several classes that make use of the same method implementation, you can use an abstract class with concrete methods to provide a default implementation.
For example, every employee has a name regardless of his or her job position. Instead of creating default implementations in every subclass, we implement a get name method in the abstract Person class. Now, we can call this method on the SoftwareEngineer class without reimplementing it. There is also no need to reimplement the name variable. We only have to provide a constructor because we somehow need to pass the name to the SoftwareEngineer class.
class SoftwareEngineer extends Person { public SoftwareEngineer(String name){ super(name); } }
To recap: You use interfaces to define contracts between objects. Interfaces are appropriate if:
- you need one method to be able to accept several objects with similar signatures but want to leave implementations entirely to the concrete classes
- another object needs to know how to interact with your object, but you haven’t created a concrete implementation yet.
You use abstract classes if:
- you need to create default implementations for methods that are used across several subclasses
- you want to provide abstract methods without implementations as well as methods with concrete implementations in one class.
Abstraction in Python
Python does not have abstract classes and interfaces out of the box. Using duck-typing and multiple inheritance the language expects you to know what type of objects you are passing and that they have the object signature required by the calling method.
However, you can implement a Python abstract class using the ABC module. It decorates methods as abstract and allows subclasses to implement the methods. We could implement the Person class as follows:
from abc import ABC, abstractmethod class Person(ABC): @abstractmethod def get_salary(self): pass
Inheriting from ABC makes the class abstract so that we cannot instantiate it. Similarly, the “@abstractmethod” decorator ensures that we cannot implement the method “getSalary”. If we try to do so, Python will give us an error at runtime. Next, we provide a concrete implementation in the SoftwareEngineer which extends the Person class.
class SoftwareEngineer(Person): def get_salary(self): return 10000
Running this returns the salary.
Now you might wonder why we need this abstract method in the first place. Since Python relies on ducktyping, wouldn’t it be enough to just implement the SoftwareEngineer class without extending the Person class?
While the benefits of an abstract class are not as obvious in Python when compared to a statically typed language, it does have the same benefit of offering a higher-level semantic contract between the classes and the caller. If you are interested in the details, check out this discussion on StackoverFlow.
Concrete Methods in Abstract Classes
As in Java, we can also provide method implementations which can be used by subclasses.
class Person(ABC): def __init__(self, name): self.name: str = name .... def get_name(self): return self.name
The get_name method requires the instance attribute name. Although we cannot instantiate an abstract class, we create a constructor to provide the name attribute and its type to the class. We are basically telling Python that in the concrete implementation of the subclass, there will be an attribute called “name” of type string (str).
In the SoftwareEngineer class, we create a constructor to pass the name attribute to the abstract superclass constructor. But we do not need to reimplement the “get_name” method.
class SoftwareEngineer(Person): def __init__(self, name): Person.__init__(self, name)
Now, we can call the getName method on the SoftwareEngineer class without explicitly implementing it there.