const - JS | lectureJavaScript OOP

Data Encapsulation - Protected Properties and Methods

JavaScript OOP

In the last lecture, we implemented a new class that showed us the need for encapsulation and data privacy. Let's now tackle this very important concept of OOP.

First, remember that encapsulation means to keep some properties and methods private/protected inside the class so that they are not accessible from outside the class. Then the rest of the methods are basically exposed as a public interface, which we can also call API.

There are two main reasons why we need data encapsulation and data privacy:

  1. Prevent code from outside of a class from accidentally manipulating the data inside the class
  2. When we expose only a small interface, i.e., a small API consisting of a few public methods, then we can change all the other internal methods with more confidence. Since we can be sure that external code does not rely these private/protected methods.

Let's now implement this in our class. Before we start, note that JavaScript does not yet support real data privacy and encapsulation. So in this lecture, we will basically fake encapsulation by using a naming convention. So the first candidate to protect here is the movements property that we have been talking about.

The movements property is mission-critical data; hence we will protect this data so that no one can accidentally manipulate it. For now, all we will do is to add an underscore _ in front of the property name.

index.js
class BankAccount {
  constructor(owner, currency, pin) {
    //...
    this._movements = [];    this.locale = navigator.language;
    console.log(`Welcome ${owner}!`);
  }

  deposit(val) {
    this._movements.push(val);  }

  //...
}

Again, this does not actually make the property truly protected because this is just a naming convention. It's something that developers agree to use, and then everyone does it this way.

So if we still wanted to access the movements property from outside the class, we could still do it.

index.js
console.log(acc1._movements);

But at least everyone in the team knows this property is protected and should not be manipulated from outside the class. Now, if we still wanted to give access to the movements property from outside the class, we could still do it. We could create a public method that returns the movements property.

index.js
class BankAccount {
  constructor(owner, currency, pin) {
    //...
    this._movements = [];
    this.locale = navigator.language;
    console.log(`Welcome ${owner}!`);
  }

  deposit(val) {
    this._movements.push(val);
  }

  getMovements() {    return this._movements;  }}

console.log(acc1.getMovements());

Or we could also create a getter for the movements property. However, most of the time functions are preferred because they are more flexible and can accept multiple arguments. There is no strict rule here, it's just a matter of preference.

index.js
class BankAccount {
  constructor(owner, currency, pin) {
    //...
    this._movements = [];
    this.locale = navigator.language;
    console.log(`Welcome ${owner}!`);
  }

  deposit(val) {
    this._movements.push(val);
  }

  get movements() {    return this._movements;  }}

Next, we could also protect the pin property. We can, for example, decide to make it read-only. Some properties can actually be set at creation time only, and then they should not be changed anymore. So we can create a getter for the pin property without a setter.

index.js
class BankAccount {
  constructor(owner, currency, pin) {
    //...
    this._pin = pin;
    this._movements = [];
    this.locale = navigator.language;
    console.log(`Welcome ${owner}!`);
  }

  deposit(val) {
    this._movements.push(val);
  }

  getMovements() {
    return this._movements;
  }

  get pin() {    return this._pin;  }}

Now if we try to change the pin property from outside the class, we will get an error.

index.js
acc1.pin = 1111; // Error

We could, in fact, make all the properties protected, but since this is just a simple example, we can just leave it as it is. To finish, let's also protect the approveLoan method which we mentionned in the previous lecture.

index.js
class BankAccount {
  constructor(owner, currency, pin) {
    //...
    this._pin = pin;
    this._movements = [];
    this.locale = navigator.language;
    console.log(`Welcome ${owner}!`);
  }

  deposit(val) {
    this._movements.push(val);
  }

  getMovements() {
    return this._movements;
  }

  requestLoan(val) {
    if (this._approveLoan(val)) {      this.deposit(val);
      console.log(`Loan approved!`);
    }
  }

  _approveLoan(val) {    return true;  }}

So this is how we protect fields from unwanted access. But as I mentioned, developers need to know about this convention and need to follow it. Because otherwise, everything will still be public.