In the past, I've often gotten confused about what sets encapsulation apart from abstraction. Both seem to be doing some level of information hiding but I never truly got the difference. Also, I often came across the usage of these words in books and blogs and felt they were out of place based on my understanding. So here is an attempt to get it right.
Encapsulation
Here is a sample definition:
"It is a simple, yet reasonable effective, system-building tool. It allows suppliers to present cleanly specified interfaces around the services they provide. A consumer has full visibility to the procedures offered by an object, and no visibility to its data. From a consumer's point of view, and object is a seamless capsule that offers a number of services, with no visibility as to how these services are implemented ... The technical term for this is encapsulation." -- [Cox, 1986]
The language facility that bundles data with the operations that perform on that data is encapsulation. Encapsulation has a lot to do with data integrity. You create well-defined interfaces that allow clients to work with your system without having to expose your internal data structure and risk inconsistencies if they were modified directly.
Let's look at some code which is a simple class that holds account information.
class BankAccountWithoutEncapsulation {
constructor(accountNumber, balance) {
this.accountNumber = accountNumber;
this.balance = balance;
}
}
const account = new BankAccountWithoutEncapsulation('123456789', 1000);
account.balance += 500; // Directly modifying balance
console.log(`New balance: ${account.balance}`);
account.balance -= 200; // Directly modifying balance
console.log(`New balance: ${account.balance}`);
balance here can be directly modified. You could go wild with setting 'yolo' to balance and js will not even complain. This is the risk we are talking about when classes do not have proper interfaces.
with encapsulation
class BankAccount {
constructor(accountNumber, balance) {
this.accountNumber = accountNumber;
this.balance = balance;
}
deposit(amount) {
if (amount > 0) {
this.balance += amount;
console.log(`Deposited ${amount}. New balance: ${this.balance}`);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.balance) {
this.balance -= amount;
console.log(`Withdrew ${amount}. New balance: ${this.balance}`);
} else {
console.log('Insufficient funds or invalid amount.');
}
}
}
const account = new BankAccount('123456789', 1000);
account.deposit(500);
account.withdraw(200);
This protects the integrity of the balance
variable while also clearly limiting the operations to be done on it. Any time you want to extend the functionality of BankAccount
, you don't have to go about tracking down all its usage and see if it will introduce any errors.
Note that encapsulation guarantees neither data protection nor information hiding. Information hiding comes into the picture only when you set out to write defensive code.
Abstraction
"Abstraction is generally defined as 'the process of formulating generalised concepts by extracting common qualities from specific examples.'"
-- [Blair et al, 1991]
"Abstraction is the selective examination of certain aspects of a problem. The goal of abstraction is to isolate those aspects that are important for some purpose and suppress those aspects that are unimportant."
-- [Rumbaugh et al, 1991]
Let's take the example of a sort method:
const strArray = ['c', 'a', 'b']
strArray.sort(); // ['a', 'b', 'c']
const numArray = [1, 12, 3, 11]
numArray.sort((a, b) => a - b); // [1, 3, 11, 12]
For simplicity let's assume we are doing a quick sort. Here is the algorithm,
Divide: In Divide, first pick a pivot element. After that, partition or rearrange the array into two sub-arrays such that each element in the left sub-array is less than or equal to the pivot element and each element in the right sub-array is larger than the pivot element.
Conquer: Recursively, sort two subarrays with Quicksort.
Combine: Combine the already sorted array.
If you notice, we are not concerned whether we are sorting numbers or floats or strings. All that the algorithm needs to work correctly is the comparator function, which can tell which amount of the 2 data points is smaller. This is an abstraction. laying out all the important steps and leaving out the part that could be plugged in.
Abstraction involves creating well-defined interfaces, classes, or modules that expose only the necessary information and operations while concealing the underlying complexities
Confusion can occur when people fail to distinguish between the hiding of information and a technique (e.g., abstraction) that is used to help identify which information is to be hidden.
Abstraction hides details at the design level, while Encapsulation hides details at the implementation level.
Encapsulate what varies
but isn't this building an abstraction? Here is the thing.
Whatever changes in the sort method will be co-located in the sort method. It encapsulates what could vary. The comparator function parameter is an abstraction that helps us re-use the encapsulated method.