Abstraction in Object Oriented Programming (OOPS)

Abstraction in Real Life

In philosophical terms, Abstraction is a process of understanding behaviour or structure of a real-life object. For example, when we think about a car, we remember an abstract concept of a car and its functionalities. This is why we can recognize object like car, even if it is different from any car we have seen before. In other words, we develop concepts of everyday objects through the process of abstraction, where we eliminate unnecessary details and focus on essential attributes and behaviour.

Real life example of abstraction in object oriented programming

Abstraction is also a common feature in real life applications. For example:

  • When we login to an online account, we enter user id and password and click login button. The process of how input data is sent to the server and verified is abstracted from us.
  • When we type on a computer, we press keys to produce results on the screen. Here we don't need to know mechanism behind how output is produced.
  • In Java, HashMap class provides methods for storing and retrieving key-value pairs. All we need to know to the use cases of HashMap in our application. The implementation details are abstracted from us.
  • When designing a class to interact with database to perform various operations, client of the class does not need to be familiar with database programming. They only need to know certain details of the class to perform database operations without a deep understanding of underlying implementation.

What is abstraction in OOPS?

Object-Oriented Programming (OOP) uses abstraction to separate the interface of an object from its internal implementation. It defines external behavior of an object and encapsulates its internal workings. This allows developers to interact with objects based on their intended behavior, without understanding the details of how the behavior is achieved.

Abstraction in oops example

In other words, abstraction in OOPS enables the hiding of the internal details of an object from the outside world, so that the focus is on what the object does, rather than how it does it.

Critical ideas to think!

  • An object has attributes and methods. By using access modifiers, we can hide important details and provide access to necessary methods and attributes to the outside world.
  • Abstraction help us define objects as abstract "actors" that can perform set of operations, change their state, and communicate with other objects.
  • We can also understand abstraction through inheritance. In inheritance hierarchy, parent classes contain general implementation, while child classes contain more specific implementation.
  • Abstraction is a key concept behind several fundamental object-oriented programming principles like Open-Closed Principle and Dependency Inversion Principle.

How do we implement abstraction in Java?

In Java, we implement abstraction using abstract classes and interfaces.

Abstraction in Java using abstract classes

An abstract class is a class that is declared with the abstract keyword and may contain both abstract methods (methods without body) and non-abstract methods. Abstract classes cannot be instantiated, but can be subclassed.

To implement abstraction using abstract classes:

  • We declare a class as an abstract class using abstract keyword. This will work as a parent class for the concrete classes.
  • Inside the abstract class, we declare abstract methods and provide default implementation for the non-abstract methods.
  • Then we create concrete subclasses by extending abstract class as a parent class. Inside concrete subclasses, we provide implementation of abstract methods defined in the abstract class.

Example Java code

abstract class Shape {
   abstract void draw();

   void fillColor(String color) {
      System.out.println("Filling color " + color + " for shape");
   }
}

class Circle extends Shape {
   void draw() {
      System.out.println("Drawing Circle");
   }
}

class Driver {
   public static void main(String[] args) {
      Shape shape = new Circle();
      shape.draw();
      shape.fillColor("Red");
   }
}

Explore this blog for more details: Abstract Class in Java

Abstraction in Java using interfaces

In Java, we can also achieve abstraction using interfaces, which serve as blueprints for classes and define methods that must be implemented. Interfaces are reference types that consist of constants, method signatures, default methods, and static methods.

  • Only default methods and static methods have method bodies.
  • Interfaces cannot be instantiated but can be implemented by classes or extended by other interfaces.

Example Java code

public interface Shape {
   int SIDES = 4;

   void draw();

   default void fillColor(String color) {
      System.out.println("Filling color " + color + " for shape");
   }

   static void printSides() {
      System.out.println("Number of sides: " + SIDES);
   }
}

class Square implements Shape {
   public void draw() {
      System.out.println("Drawing Square");
   }
}

class Driver {
   public static void main(String[] args) {
      Shape shape = new Square();
      shape.draw();
      shape.fillColor("Red");
      Shape.printSides();
   }
}

Explore this blog for more details: Interface in Java

How do we implement abstraction in C++?

In C++, abstraction is implemented using header files, access specifiers, and abstract classes.

Abstraction in C++ using abstract classes

An abstract class in C++ is a base class that cannot be instantiated as an object. It contains at least one pure virtual function, which is declared using the "virtual" keyword and the "= 0" notation in the function declaration. The purpose of an abstract class is to provide a common interface for its derived classes.

  • A derived class must implement pure virtual function(s) declared in the abstract base class, otherwise it will also become an abstract class.
  • The derived class can have its own implementation of the function.
  • Abstract classes can have constructors, but they cannot be used as a parameter type, a function return type, or the type of an explicit conversion. However, pointers and references to an abstract class are allowed.

Example C++ code

class Shape {
 public:
    virtual void Draw() = 0;
};

class Circle : public Shape {
 public:
    void Draw() { cout << "Drawing Circle" << endl; }
};

class Square : public Shape {
 public:
    void Draw() { cout << "Drawing Square" << endl; }
};

int main() {
    Shape *shape = new Circle();
    shape->Draw();
    shape = new Square();
    shape->Draw();
    return 0;
}

In summary, an abstract class is a blueprint for its derived classes, providing a common interface and enforcing the implementation of certain functions. We will discuss abstraction in C++ in a separate blogs later.

Types of Abstraction in OOPS

Object-oriented programming can be seen as an attempt to abstract both data and control. So there are two types of abstraction in OOPS: Data abstraction and Control abstraction.

Data Abstraction

When object data is hidden from the outside world, it creates data abstraction. If required, access to that data is provided through well-defined public methods, which are often called accessors and mutators (or getters and setters). These methods act as an interface between the object and the external world, providing controlled access to the object's data.

In object-oriented programming, data abstraction provides a clear separation between the internal implementation and external usage of an object. It helps developers change the internal representation or data structure of an object without affecting the code that interacts with it. So in simple words: Data abstraction promotes code modularity and improves code maintainability.

class DataAbstraction {
    private int data;

    public DataAbstraction(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }
    
    // Other private or public methods working on data
}

Control Abstraction

In object-oriented programming, control abstraction is the abstraction of actions. Often, we don't need to provide details about all the methods of an object. In other words, when we hide the internal implementation of the different methods related to the client operation, it creates control abstraction.

interface Bank {
    double getBalance();
    void deposit(double amount);
    void withdraw(double amount);
}

class SavingsAccount implements Bank {
    private double balance;
    
    public SavingsAccount(double balance) {
        this.balance = balance;
    }
    
    @Override
    public double getBalance() {
        return balance;
    }
    
    @Override
    public void deposit(double amount) {
        balance = updateBalance(balance, amount, true);
    }
    
    @Override
    public void withdraw(double amount) {
        balance = updateBalance(balance, amount, false);
    }
    
    private double updateBalance(double balance, double amount, boolean deposit) {
        if (deposit) {
            return balance + amount;
        } else {
            return balance - amount;
        }
    }
}

class Driver {
    public static void main(String[] args) {
        Bank account = new SavingsAccount(1000.0);
        System.out.println("Initial balance: " + account.getBalance());
        
        account.deposit(500.0);
        System.out.println("Balance after deposit: " + account.getBalance());
        
        account.withdraw(200.0);
        System.out.println("Balance after withdrawal: " + account.getBalance());
    }
}

In the above example, client code can simply create an instance of the SavingsAccount class and use it to perform banking operations without having to know how operations are done internally. The updateBalance method is a private method that performs actual update of the balance and is only accessible within the class. This creates control abstraction.

We can also understand control abstraction at a lower level. For example, a software code is a collection of methods and many times, some of these methods are similar and repeated multiple times. The idea of control abstraction is to identify these methods and combine them into a single unit.

Note: Control abstraction is one of the primary purposes of using programming languages. Computer machines understand operations at a very low level, such as moving some bits from one memory location to another location and producing the sum of two sequences of bits. Programming languages allow this to be done at a higher level.

Example to understand control abstraction and data abstraction together

Suppose we define an abstract data type called Dictionary, where each key is associated with a unique value and we can access values based on their keys. This data structure may be implemented using a hash table, a binary search tree, or even a simple array. For the client code, the abstract properties are the same in each case.

// Dictionary Interface
interface Dictionary {
    void putValue(String key, Integer value);
    Integer getValue(String key);
}

// HashTable implementation of Dictionary
class HashTable implements Dictionary {
    private Map<String, Integer> map = new HashMap<>();

    public void putValue(String key, Integer value) {
        map.put(key, value);
    }

    public Integer getValue(String key) {
        return map.get(key);
    }
}

// BinarySearchTree implementation of Dictionary
class BinarySearchTree implements Dictionary {
    private class Node {
        String key;
        Integer value;
        Node left, right;

        Node(String key, Integer value) {
            this.key = key;
            this.value = value;
        }
    }
    
    private Node root;
    
    public void putValue(String key, Integer value) {
        root = put(root, key, value);
    }

    private Node put(Node node, String key, Integer value) {
        // implementation code
    }

    public Integer getValue(String key) {
        Node node = get(root, key);
        return node == null ? null : node.value;
    }

    private Node get(Node node, String key) {
        // implementation code
    }
}

// Driver code
public class Main {
    public static void main(String[] args) {
        Dictionary dict = new HashTable();
        dict.putValue("A", 1);
        dict.putValue("B", 2);
        System.out.println("Value of key 'B': " + dict.getValue("B"));

        dict = new BinarySearchTree();
        dict.putValue("A", 1);
        dict.putValue("B", 2);
        System.out.println("Value of key 'B': " + dict.getValue("B"));
    }
}

Control abstraction is present in the above example because the client code interacts with the Dictionary interface, which defines the actions that can be performed (putValue and getValue). The client code doesn't need to know the internal implementation details of the methods in the HashTable and BinarySearchTree classes. In simple terms, it abstracts the control flow of these methods and provides a simple way of accessing and manipulating values in the dictionary.

Data abstraction is also present in the above example because it hides the internal implementation of the data structures. The client code only accesses the data through the defined methods, without knowing how the data is stored internally (e.g., using a HashMap or a binary search tree). In simple terms, the internal implementation of the data structure is abstracted away. This provides a higher-level view and allows flexibility in choosing different data structure implementations without affecting the client code.

Abstraction vs. Encapsulation Comparison

A well-designed code with proper use of abstraction follows the Principle of Least Astonishment: “A component of a system should behave in a way that most users will expect it to behave. The behavior should not surprise users”.

But the critical question is: How to identify and expose that expected behavior to the users? How to handle their implementation details? At this stage, the next pillar of object-oriented programming comes into the picture: encapsulation!

Difference between Abstraction and Encapsulation

Abstraction vs. Encapsulation Comparison

Advantages of Abstraction in OOPS

  • Simplifies code and reduces complexity: Abstraction is used to create a boundary between the application and the client code, which can help reduce the implementation complexity of software.
  • Facilitates modular and flexible design: Abstraction helps us divide responsibilities into software entities (classes, methods, etc.), where user only know necessary functionalities, but not how that functionality is implemented.
  • Improves code readability, reusability and maintainability: Abstraction maximizes the ease of use of relevant information. In other words, code with proper abstraction helps programmers quickly understand what the code does and how it is meant to be used.
  • Abstraction allows programmers to simplify programming and shift focus from implementation details and avoid writing low-level code. On other side, It also allows programmers to change the internal implementation of methods or concrete classes without affecting the interface.
  • Enhances code security by hiding internal implementation details and enables encapsulation of data and behavior within objects.
  • Supports extensibility, scalability and evolutionary development of code.

Conclusion

In object oriented programming, Abstraction is the practice of only exposing the necessary details to the client or user. This means that when a client uses a class, they don't need to know the inner workings of the class's operations. This decouples the user of the object from its implementation, making it easier to understand and maintain. If there is a change in an operation, only the inner details of the related method need to be updated.

Enjoy learning, Enjoy OOPS!

More from EnjoyAlgorithms

Self-paced Courses and Blogs