자바스크립트 OOP [2] – 클래스 상속, 캡슐화(protected / private)

Class 상속 – 생성자 함수

  • 이전 글에서 공부한 Prototypal Inheritance 개념을 개발자가 생성한 클래스 간에 생성해주려면 어떻게 해야할까?
const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};

Person.prototype.calcAge = function () {
  console.log(2037 - this.birthYear);
};

const Student = function (firstName, birthYear, course) {
  Person.call(this, firstName, birthYear);
  this.course = course;
};

// Linking prototypes
Student.prototype = Object.create(Person.prototype);

Student.prototype.introduce = function () {
  console.log(`My name is ${this.firstName} and I study ${this.course}`);
};
  • Object.create로 Person과 Student 링크해준다.
  • 자식 프로토타입 내부에서는 call 메소드를 호출해서 부모 프로토타입의 프로퍼티를 초기화해준다.
console.log(mike instanceof Student); // true
console.log(mike instanceof Person); // true
console.log(mike instanceof Object); // true

console.log(mike.__proto__); // Person(prototype which contains introduce function) -> bug
console.log(mike.__proto__.__proto__); // prototype which contains calcAge function

Student.prototype.constructor = Student;
console.dir(Student.prototype.constructor); // Student
  • Object.create로 링크해준 경우 mike.__proto__가 Person으로 나오게 되는데, 이 경우 constructor를 Student로 정정해줄 수 있다.

Class 상속 – ES6 Classes

class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }

  // Instance methods
  calcAge() {
    console.log(2037 - this.birthYear);
  }

  greet() {
    console.log(`Hey ${this.fullName}`);
  }

  get age() {
    return 2037 - this.birthYear;
  }

  set fullName(name) {
    if (name.includes(' ')) this._fullName = name;
    else alert(`${name} is not a full name!`);
  }

  get fullName() {
    return this._fullName;
  }

  // Static method
  static hey() {
    console.log('Hey there 👋');
  }
}

class StudentCl extends PersonCl {
  constructor(fullName, birthYear, course) {
    // Always needs to happen first!
    super(fullName, birthYear);
    this.course = course;
  }

  introduce() {
    console.log(`My name is ${this.fullName} and I study ${this.course}`);
  }

  calcAge() {
    console.log(
      `I'm ${
        2037 - this.birthYear
      } years old, but as a student I feel more like ${
        2037 - this.birthYear + 10
      }`
    );
  }
}
  • ES6 클래스의 경우 extends 키워드와 super를 사용해서 링크해줄 수 있다.

Class 상속 – Object.create

const PersonProto = {
  calcAge() {
    console.log(2037 - this.birthYear);
  },

  init(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  },
};

const steven = Object.create(PersonProto);

const StudentProto = Object.create(PersonProto);
StudentProto.init = function (firstName, birthYear, course) {
  PersonProto.init.call(this, firstName, birthYear);
  this.course = course;
};

StudentProto.introduce = function () {
  console.log(`My name is ${this.firstName} and I study ${this.course}`);
};

const jay = Object.create(StudentProto);
jay.init('Jay', 2010, 'Computer Science');
jay.introduce();
jay.calcAge();
  • PersonProto를 상속하는 StudentProtoObject.create로 연결해준 후 부모의 프로퍼티와 자식의 프로퍼티를 초기화해 줄 init 메소드를 생성해준다.

캡슐화 – protected, private

// Encapsulation: Protected Properties and Methods
// Encapsulation: Private Class Fields and Methods

// 1) Public fields
// 2) Private fields
// 3) Public methods
// 4) Private methods
// (there is also the static version)

class Account {
  // 1) Public fields (instances)
  locale = navigator.language;

  // 2) Private fields (instances)
  #movements = [];
  #pin;

  constructor(owner, currency, pin) {
    this.owner = owner;
    this.currency = currency;
    this.#pin = pin;

    // Protected property
    // this._movements = [];
    // this.locale = navigator.language;

    console.log(`Thanks for opening an account, ${owner}`);
  }

  // 3) Public methods

  // Public interface
  getMovements() {
    return this.#movements;
  }

  deposit(val) {
    this.#movements.push(val);
    return this;
  }

  withdraw(val) {
    this.deposit(-val);
    return this;
  }

  requestLoan(val) {
    // if (this.#approveLoan(val)) {
    if (this._approveLoan(val)) {
      this.deposit(val);
      console.log(`Loan approved`);
      return this;
    }
  }

  static helper() {
    console.log('Helper');
  }

  // 4) Private methods
  // #approveLoan(val) {
  _approveLoan(val) {
    return true;
  }
}
  • protected 프로퍼티나 메소드를 작성하려면 _ 키워드를 사용하면 된다. 해당 키워드가 사용된 경우 인스턴스 단에서 사용하지 않도록 암묵적인 약속을 한것과 같으나, 실제로 호출은 가능하다.
  • 아예 호출이 불가능한 private 프로퍼티나 메소드를 작성하려면 # 키워드를 사용하면 된다.
    • Uncaught SyntaxError가 발생한다.
const acc1 = new Account('Jonas', 'EUR', 1111);

// acc1._movements.push(250);
// acc1._movements.push(-140);
// acc1.approveLoan(1000);

acc1.deposit(250);
acc1.withdraw(140);
acc1.requestLoan(1000);
console.log(acc1.getMovements());
console.log(acc1);
Account.helper();

// console.log(acc1.#movements);
// console.log(acc1.#pin);
// console.log(acc1.#approveLoan(100));

메소드 체이닝

// Chaining
acc1.deposit(300).deposit(500).withdraw(35).requestLoan(25000).withdraw(4000);
console.log(acc1.getMovements());
  • 클래스의 메소드도 체이닝해서 사용할 수 있다.

Leave a Reply

Your email address will not be published. Required fields are marked *