const - JS | lectureJavaScript OOP

Data Encapsulation

JavaScript OOP

There are a few more things we need to learn about classes, so let's create a new class called BankAccount.

index.js
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.

index.js
const noah = new BankAccount('Noah Powers', 'USD', 1916);

console.log(noah);

BankAccount instance

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:

index.js
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.

index.js
class BankAccount {
  constructor(owner, currency, pin) {
    //...
    this.movements = [];    this.locale = navigator.language;
  }
}

BankAccount instance

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.

index.js
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:

index.js
noah.movements.push(272); // deposit
noah.movements.push(-203); // withdrawal

console.log(noah);

BankAccount instance

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.

index.js
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.

index.js
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:

index.js
noah.movements.push(149);

The same goes, for example, for the pin property. We can access the pin from outside the class:

index.js
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.

index.js
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.

index.js
noah.requestLoan(1651);

But, of course, we are also able to do this:

index.js
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.