The 4 Principles of Object-Oriented Programming (A.P.I.E)

The 4 Principles of Object-Oriented Programming (A.P.I.E)
Photo by Alex Lvrs / Unsplash

My roommate and I were recently talking about what a good technical interview for a new grad would consist of. Based on our work experience, we were trying to come up with the principles that interviewees should be comfortable with. One concept that I think is important for all software engineers to know about is the 4 principles of object-oriented programming: A.P.I.E. No I’m not talking about a pie, though I could go for some right now. I’m talking about abstraction, polymorphism, inheritance, and encapsulation.

Abstraction

In order to reduce the complexity for users, we expose only the essential details of an entity and hide the irrelevant. This is called abstraction. By hiding the information only relevant to the context, we can create entities similar to things in the real world. There are 2 types of abstraction:

  • Data Abstraction: This is where we create complex data types from smaller data types.
  • Control Abstraction: This is where we hide the logic of a complex sequence of actions behind method calls. This way, the logic is hidden from the caller and can be easily changed in the future.

Polymorphism

Polymorphism is the ability to let entities take on different meanings in different contexts. We can either implement functions or reference variables that behave differently based on the programmatic context. The kind of polymorphism an object undergoes depends on two things: (1) the timing of the object's transformation and (2) which aspect of the object is changing. An object can either transform at compile-time or runtime and the aspect of an object that is changing can either be a method or an object.

There are 4 main types of polymorphism:

Subtype Polymorphism (Runtime)

This type of polymorphism uses one class name to reference multiple kinds of subtypes simultaneously. For example, we can create a class like “Car” and define multiple subtypes like “Altima”, “Bolt”, “Accord”, etc. Each of the subtypes will share properties of the “Car” class. A method can accept all “Car” objects by using the Car type for the parameter.

Parametric Polymorphism (Generics)

This type of polymorphism allows for the creation of generic code that can operate on a wide range of data types. In this case, types are passed in as parameters when creating a class or calling a method. In Java, we see this type of polymorphism when creating generic classes or generic methods.

Generic Classes in Java

public class SomeClass<X> {
	...
}

Generic Methods in Java

public <X> Collection<X> someMethod(X someParam) {
	...
}

Ad hoc Polymorphism (Compile-time overloading)

This type of polymorphism is where a function or operator can behave differently depending on the types of its arguments.

  • Overloading Method Names: When overloading method names, we’ll see that the same method name can be used but different parameter types are passed in. One good example of this can be seen in the Math class in java.lang package, where the method for calculating absolute value, abs(…), is defined 4 times and can take an int value, double value, float value, or long value. In this case, all 4 functions are named the same, but they assume different forms based on the parameter types.
  • Overloading Operators: When operators are overloaded, the operator behaves differently based on the type of values being operated on. For example, with the + operator, if the values are integers then the operator will be indicative of an additive process. In the case of string values, the operation applied to the values will be a concatenation of the two strings.

Coercion Polymorphism (Casting)

This type of polymorphism is where the type of the value will either be changed implicitly based on the context of its usage or explicitly. Implicit coercion is where a compiler will automatically convert a value from one type to another to make it compatible with the context in which it is used. An explicit cast can also be done on the value using a casting expression defined by the programming language.

Inheritance

Inheritance is used to derive a new type from an existing type, thereby establishing a parent-child relationship. Inheritance allows a new class (the child or subclass) to extend or modify the behavior of an existing class (the parent or superclass) while maintaining the original functionality. The child class that inherits attributes and methods from the parent class can modify the behavior of existing methods by overriding them and can add new methods that are specific to the child class. The purpose of inheritance is to promote code reuse and improve the maintainability of the code base.

Encapsulation

Encapsulation is the process of bundling data and operations on the data together in an entity. By wrapping the data and methods within classes, a new data type can be created for more complex behavior. There are two concepts that are prevalent with encapsulation: information hiding and implementation hiding.

  • Information hiding is done through the use of access control modifiers (public, private, protected). It refers to the practice of hiding the internal details of the class from the outside world. This means that the implementation details of a class, including its data and methods, are kept hidden from other classes.
  • Implementation hiding is done through the creation of an interface for a class. The internal implementation details of a class are hidden and not directly accessible to other classes. This ensures that any changes made to the implementation of the class do not impact the class's external behavior or interface.

For more readings on these topics, here are some links that may help: