Encapsulation is a fundamental concept in object-oriented programming (OOP) that involves bundling data and the methods that operate on that data within a single unit, known as a class. This concept helps to protect the data and methods from outside interference, as it restricts direct access to them. In other words, encapsulation involves wrapping data and methods within a class to create a protective barrier around them.
In the book "Object-Oriented Analysis and Design," Grady Booch defines encapsulation as "the process of compartmentalizing the elements of an abstraction that constitute its structure and behavior; encapsulation separates the contractual interface of an abstraction and its implementation." In other words, encapsulation helps to separate the interface (public-facing aspect of a class) from the implementation (internal workings of the class). This allows for flexibility in the design of a class, as the implementation can be modified without affecting the interface.
If this definition of encapsulation is not clear to you, it may be because you are not familiar with the concept of abstraction. It is common for programmers to confuse encapsulation and abstraction, as they are related concepts. However, they are distinct ideas and it is important to understand the differences between them. We have covered this difference in the last section of this blog.
Up to this point, we might think that what is so special about encapsulation or how all this idea makes sense? Well, think about these lines: The true value of encapsulation is recognised in an environment that is prone to change. If our code is well-encapsulated, we can better manage risk in the event of a requirement change. By encapsulating the code, we are less likely to change insignificant part (for us) of the code.
We've learned that encapsulation involves bundling data and related methods in order to protect data and facilitate communication. Let's look at an example to further illustrate this concept.
Imagine a car (any model will work). Now, think about the various components that make up the car, such as tires, wheel, engine, and shafts. These components can be thought of as data attributes of the class "Car." Now, consider actions that a car can perform, such as moving, steering, honking, etc. These actions can be thought of as the methods of the "Car" class.
In the above example, we can see how encapsulation is used to group together data attributes within the "Car" class, along with the methods that operate on those components. This bundling helps to abstract the inner workings of the car from the user. For example, we don't need to know how the engine or shafts work in order to drive the car.
We simply use the interface provided by the methods (such as steering and moving) to complete the task. This is the essence of abstraction, where client is only concerned about the interface to achieve a specific goal, without worrying about the internal mechanism. So encapsulation and abstraction work together to create a clear separation between the interface and implementation of a class, making it easier to understand and maintain the code.
Let’s see another real-life example with code:
class Employee
{
private:
int ID;
string employeeName;
int joinYear;
public:
Employee(int id, string name, int year)
{
ID = id;
employeeName = name;
joinYear = year;
}
int getId()
{
return ID;
}
string getName()
{
return employeeName;
}
int getYear()
{
return joinYear;
}
void setId(int newID)
{
ID = newID
}
void setName(string newName)
{
employeeName = newName
}
void setYear(int newYear)
{
joinYear = newYear
}
};
In this example, "Employee" class has been defined with three data members (ID, name, and joinyear) and six methods (getId(), getName(), getYear(), setId(), setName(), and setYear()). This code demonstrates encapsulation in several ways:
class Student
{
private String studentName;
private int studentRollNumber;
private int studentAge;
public Student(String name, int rollNumber, int age)
{
studentName = name;
studentRollNumber = rollNumber;
studentAge = age;
}
public int getAge()
{
return studentAge;
}
public String getName()
{
return studentName;
}
public int getRollNumber()
{
return studentRollNumber;
}
public void setAge(int newAge)
{
studentAge = newAge;
}
public void setName(String newName)
{
studentName = newName;
}
public void setRoll(int newRoll)
{
studentRollNumber = newRoll;
}
}
Each object encapsulates some data and methods. The object takes requests from other client objects without exposing details of its data or methods. The object alone is responsible for its own state, declaring private data and methods for the implementation and exposing the public interface for clients. The client depends on the public interface and does not depend on details of the implementation.
Note: To experience the power of encapsulation, one should know getter and setter methods. By only using getter methods in a class, one can make a read-only class. Similarly, by only using setter methods, one can make a write-only class.
There are three basic techniques to implement encapsulation in object oriented programming.
Data Member Encapsulation: Data members can be defined as private members of the Class. Setters and Getters methods should be used by any object that wants to change or retrieve the value of a data member.
Method Encapsulation: We can hide methods used for internal implementation that does not need to be visible to the public. Such method should be declared as private so that the user does not have access to it.
Class Encapsulation: Our implementation might contain an internal implementation of a class that stores and processes specific information. We encapsulate that class by defining it as private and hiding it from user access. These classes should not be included in any public interface.
Object-oriented programming provide access modifiers to control visibility and accessibility of class-level structures and hide sensitive data from users. Programmers should use these access modifiers to differentiate between public and non-public interface of an object.
Access modifiers are used to restrict the scope of a class, constructor, variable, method, or data member. It sets some restrictions on the class members not to get directly accessed by the outside functions. In object-oriented programming, there are four different types of access modifiers:
The public access modifier has a broad scope. It implies that public class members (classes, methods, or data members) can be accessed using object of other classes. In other words, public class members have no restriction on the scope, and they can be accessible from everywhere in the program.
The class members declared private are limited to the scope of the class and can be accessed only by the member methods inside the class. In other words, they can not be accessed directly by any object or method outside the class.
A protected access modifier is similar to a private access modifier, but access level is limited to the same class or any subclass inherited from that class. This access through inheritance can access base class elements in the derived class depending on the mode of inheritance.
When no access modifier is specified for a class, method, or data member, it is said to have the default access modifier. In other words, the default members’ access is limited to the current or the same package. Classes not in the same package cannot access or use the default members.
To understand the difference, we need to understand the idea of abstraction. In abstraction, we focus on the outside view of an object and separate essential behavior from its implementation. To encapsulate abstraction, here’s an extract from the same book of Grady Booch: "An abstraction denotes the essential characteristics of an object that distinguish it from all other kinds of objects and thus provide crisply defined conceptual boundaries, relative to the perspective of the viewer."
Let’s see how they are different from each other.
After summing up the differences, one can say that an abstraction consists of how an object and its behaviors are presented to the user (interface) and encapsulation is a methodology that helps the programmer create this interface!
For example, consider the keyboard on your computer. When you press a key, it acts as a mechanical switch that closes an electrical circuit. The computer then processes this active circuit to produce the desired result. The details of this processing are not relevant to the general user and are therefore abstracted. Additionally, all circuits and processing mechanisms are encapsulated within the keyboard or elsewhere in the machine.
This beauty of encapsulation helps achieve abstraction and makes complex implementations look as easy as pressing a key.
As we already know, encapsulation is about designing classes for both private implementation and public interface. But what does encapsulation have to do with an inheritance? How does inheritance break the principle of encapsulation?
Suppose we create a subclass and inherit implementation from a base class. Any changes made to the base class's implementation will affect all subclasses in the class hierarchy. This is because inheritance creates a strong connection between classes, but weakens encapsulation within a class hierarchy. In other words, inheritance is a mechanism for strong encapsulation between different classes, but weak encapsulation between a base class and its derived classes.
So here is the summary of the comparison between inheritance and abstraction:
Encapsulation can provide several benefits to a software system:
Enjoy learning, Enjoy algorithms, Enjoy OOPS!