What is “A PIE” ? “A PIE” is an abbreviation for “Abstraction, Polymorphism, Inheritance, Encapsulation”, four pillars of object-oriented programming. These concepts plays a crucial role in creating a robust, maintainable, and flexible codebase. Let’s delve deeper into each of these concepts.

Abstraction

Abstraction is hiding information that you don’t need to know to use a method or a class. It means that a lot of details of a class or an object are stripped away to reduce something to its core essence.

Polymorphism

Polymorphism is a way of implementing abstract functionality. It is an ability to react to different objects, yet share the same methods.

Inheritance

Inheritance is a mechanism to represent groups of similar types. It allows a class to inherit properties and behaviors from another class.

Encapsulation

Encapsulation is wrapping code and data into a single unit, limiting access to some of that data. It prevents external code from being concerned with internal workings of an object.

Example

A company’s positions are divided into two roles: manager, and engineer. I will develop a system to retrieve data about each employee.

First, I will create an Employee class to represent these roles.

1
2
3
4
5
6
7
public abstract class Employee {
  String firstName;
  String lastName;
  Long salary;

  abstract void getData();
}
1
2
3
4
5
public class Engineer extends Employee {
  void getData() {
    System.out.println("[ENGINEER] First Name: " + firstName + "\nLast Name: " + lastName + "\nSalary: " + salary);
  }
}
1
2
3
4
5
public class Manager extends Employee {
  void getData() {
    System.out.println("[MANAGER] First Name: " + firstName + "\nLast Name: " + lastName + "\nSalary: " + salary);
  }
}

Engineer and Manager classes are an example of inheritance, as they share similar attributes: first name, last name, and salary. They extend Employee class to inherit this data instead of recreating the same attributes.

Additionally, getData illustrates abstraction. We don’t need to print the information by getting the value of first name, last name, and salary data; calling the getData method is enough.

The existing implementation is not secure because anyone can modify the data. This happens because the properties are not properly protected. Let’s enhance the security by adding access modifiers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public abstract class Employee {
  private String firstName;
  private String lastName;
  private Long salary;

  public abstract void getData();

  public String getFirstName() {
    return this.firstName;
  }

  public String getLastName() {
    return this.lastName;
  }

  public String getSalary() {
    return this.salary;
  }

  protected void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  protected void setLastName(String lastName) {
    this.lastName = lastName;
  }

  protected void setSalary(Long salary) {
    this.salary = salary;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Engineer extends Employee {
  public Engineer(String first, String last, Long salary) {
    setFirstName(first);
    setLastName(last);
    setSalary(salary);
  }

  public void getData() {
    System.out.println("[ENGINEER] First Name: " + getFirstName() + "\nLast Name: " + getLastName() + "\nSalary: " + getSalary());
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Manager extends Employee {
  public Manager(String first, String last, Long salary) {
    setFirstName(first);
    setLastName(last);
    setSalary(salary);
  }

  public void getData() {
    System.out.println("[MANAGER] First Name: " + getFirstName() + "\nLast Name: " + getLastName() + "\nSalary: " + getSalary());
  }
}

By adding access modifiers, we are practicing encapsulation concept because the access to data is limited and exposed through getter functions.

Let’s create a test class

1
2
3
4
5
6
7
8
9
public class EmployeeSimulator {
  public static void main(String[] args) {
    Employee engineer = new Engineer("Foo", "Bar", 100L);
    Employee manager = new Manager("Foo2", "Bar2", 250L);

    engineer.getData();
    manager.getData();
  }
}

Output:

[Engineer] First Name: Foo
Last Name: Bar
Salary: 100
[MANAGER] First Name: Foo2
Last Name: Bar2
Salary: 250

This is an implementation of polymorphism where the Employee class responds differently to the getData function when it is used by the Engineer and Manager classes.