Data Encapsulation
There are a few more things we need to learn about classes, so let's create a new class called BankAccount
.
class BankAccount {
constructor(owner, currency, pin) {
this.owner = owner;
this.currency = currency;
this.pin = pin;
}
}
Now, let's create a new instance of this class.
const noah = new BankAccount('Noah Powers', 'USD', 1916);
console.log(noah);
As expected, we get an object with the properties we defined in the constructor function. But now, what if we wanted to add a movements
array and for example a locale
property to this class? For the movements
array, we would like to start with an empty array, and for the locale
property, we will get it from navigator.language
.
We could do something like this:
class BankAccount {
constructor(owner, currency, pin, movements) { //...
this.movements = movements; }
}
const noah = new BankAccount('Noah Powers', 'USD', 1916, []);
And this will be perfectly fine. However, passing an empty array as an argument to the constructor function doesn't make much sense. Hence, instead of passing an empty array, we can just create an empty array inside the constructor function.
class BankAccount {
constructor(owner, currency, pin) {
//...
this.movements = []; this.locale = navigator.language;
}
}
We can also execute any code we want inside the constructor function. For example, wan can greet the owner once he creates a new bank account.
class BankAccount {
constructor(owner, currency, pin) {
//...
this.movements = [];
this.locale = navigator.language;
console.log(`Welcome ${owner}!`); }
}
Now, let's talk about withdrawals and deposits. That is, let's talk about our movements
array. If we want to deposit or remove money from our bank account, we could simply do something like this:
noah.movements.push(272); // deposit
noah.movements.push(-203); // withdrawal
console.log(noah);
As you can see it worked perfectly fine. However, it is not a good idea at all to manipulate the movements
array directly. So instead of interacting with a property directly as we did above, it is a lot better to create methods that will interact with these properties. And that is especially true for important properties like movements
. This helps us to keep our code clean and organized and also helps us to avoid bugs as our code grows.
So, let's create a deposit
and a withdraw
method.
class BankAccount {
constructor(owner, currency, pin) {
//...
this.movements = [];
this.locale = navigator.language;
console.log(`Welcome ${owner}!`);
}
deposit(val) { this.movements.push(val); }
withdraw(val) { // Yes 😉, we can call other methods inside a method this.depsoit(-val); }}
With this in place, we can now deposit and withdraw in a much cleaner way.
noah.deposit(272); // deposit
noah.withdraw(203); // withdrawal
The result will be the same as before, but now we are using the public interface of our class, which is a lot cleaner and more organized. Also, the withdraw
method abstracts the fact that a withdrawal is a negative movement. Earlier, when we did it manually, we needed to pass in -203
. But now, as we make a withdrawal, it's, of course, more natural to pass in 203
, and the withdraw
method will take care of the rest.
What I'm trying to say is that the minus
sign is something that the user of the the object shouldn't have to worry about.
Next, nothing stops someone on our team from interacting with the movements
array directly, potentially making mistakes and introducing bugs. So having the deposit
and withdraw
methods in place doesn't make it impossible to do this still:
noah.movements.push(149);
The same goes, for example, for the pin
property. We can access the pin from outside the class:
console.log(noah.pin); // 1916
This shouldn't be possible. And this is a very real and important concern. I'm not just telling you something theoretical here. And the same, of course, for methods. Let's assume we have a requestLoan
method in our class.
class BankAccount {
constructor(owner, currency, pin) {
//...
this.movements = [];
this.locale = navigator.language;
console.log(`Welcome ${owner}!`);
}
deposit(val) {
this.movements.push(val);
}
withdraw(val) {
this.depsoit(-val);
}
requestLoan(val) { if (this.approveLoan(val)) { this.deposit(val); console.log(`Loan approved!`); } }
approveLoan(val) { return true; }}
In the public interface, we basically only want to expose the requestLoan
method.
noah.requestLoan(1651);
But, of course, we are also able to do this:
noah.approveLoan(1651);
In this case, the approveLoan
method does not do anything, but we should not even be allowed to access this kind of method in a real-world scenario. This is kind of an internal method that only the requestLoan
method should be able to call. Hence, I'm telling you all this to justify the fact that we need data encapsulation and data privacy in our classes. And this is exactly what we will learn about in the next lecture.