TypeScript Study day-4_ OOP-Encapsulation

hwibaski·2021년 12월 22일
1

TypeScript & JavaScript

목록 보기
14/15

드림코딩엘리님의 TypeScript 강의를 토대로 정리했습니다.

1. Encapsulation이란?

객체지향의 원칙 중 하나로 특정 키워드를 사용함으로서 정의한 클래스 외부에서 클래스 내부의 메서드나 멤버변수, 프로퍼티의 접근을 막기 위해 사용한다. 캡슐화라고도 부르며, 클래스 내부의 정보를 은닉하기 보호하기 위해 사용한다. 캡슐화를 위한 키워드는 public, private, protected가 있다.

  • public : 생략 시 기본적으로 public, 외부에서 접근 가능
  • private : 지정하면 외부에서는 접근할 수 없다.
  • protected : 외부에서는 접근할 수 없지만 해당 클래스르 상속한 자식 클래스에서는 접근이 가능하다.

1-1. 예시

우리는 이전의 커피기계를 찍어내는 클래스를 만들고 있다. 멤버 변수로는 커피 원 샷을 만들기 위해서 필요한 양을 상수로 지정했고, 생성자 함수에 매개변수로 넣어주기 위해 우리가 가지고 있는 커피 콩의 양을 지정했다. 이 클래스 외부에서 샷을 만들기 위해 필요로 하는 커피콩의 양과 우리가 가지고 있는 커피 콩의 양을 임의로 조절한다면 위험하다. 가령 (6)의 maker.coffeeBeans = -34같은 값을 넣는다면 말도 안되는 일이 벌어질 수 있는 것이다. 그렇기 때문에 클래스 외부에서는 접근이 불가능 하도록, (1), (2)의 코드를 private 으로 선언한 것이다.

type CoffeeCup = {
  shots: number;
  hasMilk: boolean;
};

class CoffeeMaker {
  private static BEANS_GRAMM_PER_SHOT: number = 7; // (1)
  private coffeeBeans: number = 0; // (1)

  // contructor를 private로 설정 시 인스턴스를 만들어주는 static한 함수를 따로 만들어 놓아야한다.
  private constructor(coffeeBeans: number) { // (2)
    this.coffeeBeans = coffeeBeans;
  }
		
  static makeMachine(coffeeBeans: number): CoffeeMaker { // (3)
    return new CoffeeMaker(coffeeBeans);
  }

  fillCoffeeBeans(beans: number) {           // (4)
  // 개발자가 beans를 0보다 작게 설정할 경우 error 메세지를 던져주기.
    if (beans < 0) {
      throw new Error('value for beans should be greater than 0');
    }
    this.coffeeBeans += beans;
  }

  makeCoffee(shots: number): CoffeeCup {
    if (this.coffeeBeans < shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT) {
      throw new Error('Not enough coffee beans!');
    }
    this.coffeeBeans -= shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT;
    return {
      shots,
      hasMilk: false,
    };
  }
}

const maker = new CoffeeMaker(32);
const maker2 = CoffeeMaker.makeMachine(32);
maker.fillCoffeeBeans(10);
// 인스턴스의 coffeeBeans는 음수가 되면 안 된다. 커피 콩의 갯수가 음수는 말 이 안된다.
// 멤버 변수가 private으로 설정됐기 때문에 접근이 불가능하다.
--------------------------------------------------
CoffeeMaker.BEANS_GRAMM_PER_SHOT; // dangerous
maker.coffeeBeans = 3; // valid but dangerous
maker.coffeeBeans = -34; // invalid (6)
--------------------------------------------------

1-2. private으로 설정한 멤버 변수를 조작할 수 있는 메서드 생성

우리는 이제 캡슐화를 통해 클래스 내부에 있는 멤버변수를 외부로부터 안전하게 보호하고 있다. 캡슐화한 멤버 변수는 어떻게 조작을 해야할까? 클래스 내부에서 해당 멤버변수를 조작할 수 있는 메서드를 만들어주어야한다. 사용자는 오직 이 메서드를 통해서만 멤버 변수의 값을 조작할 수 있다. (4)의 코드를 살펴보자. fillCoffeeBeans() 내부에 사용자가 유효하지 않은 값을 넣었을 때 에러를 던져주는 로직을 추가해줄 수도 있다.

1-3. 생성자 함수는 왜 private으로 선언한 것인가?

우리는 이전 챕터에서 static을 활용하여 클래스 외부에서 생성자 함수를 사용하지 않고 인스턴스를 생성할 수 있다는 것을 알게 됐다. (2), (3)의 코드를 보자. 생성자 함수를 private하게 만들어서 외부에서 접근할 수 없게 만들고, 클래스 내부에 makeMachine 메서드를 통해서 인스턴스를 생성할 수 있게 유도하는 것이다. 이러한 방법을 팩토리 패턴이라고 한다. 팩토리 패턴은 객체를 생성하는 코드를 분리하여 결합도(의존성)을 낮추어 확장성을 증가시키고 코드 수정을 용이하게 하기 위한 패턴이다.

현재로서는 팩토리 패턴에 경험이 부족해 잘 와닿지 않는 내용입니다. 추후에 내용 보충하겠습니다.

2. Getter & Setter

  • getter와 setter는 멤버 변수처럼 사용이 가능하다.
  • 멤버 변수의 값을 변경하거나 설정할 때 조금 더 유연하게 사용이 가능하다.
  • 유연하게 사용함과 동시에 프로퍼티 값을 원하는 대로 통제할 수 있다.
  • 유저의 firstName과 lastName을 받아서 fullName을 만들어주는 User라는 클래스가 있다.
class User {
  firstName: string;
  lastName: string;
  fullName: string;
  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = `${firstName} ${lastName}`;
  }
}
const user = new User('Steve', 'Jobs');
console.log(user.fullName); // Steve Jobs
// 우리는 firstName을 다른 이름으로 바꾸고 싶다.
user.firstName = 'Hwimin';
// 하지만, 결과는 처음에 constructor에 넣은 'Steve Jobs'가 출력된다.
// constructor에 최초에 지정된 값이 그대로 출력되고 있다.
console.log(user.fullName); // Steve Jobs

2-1. Getter의 등장

  • Getter는 멤버 변수의 값을 받아서 새로운 값을 출력할 때 유용한다.
class User {
  firstName: string;
  lastName: string;
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
const user = new User('Steve', 'Jobs');
console.log(user.fullName); // Steve Jobs
user.firstName = 'Hwimin';
console.log(user.fullName); // Hwimin Jobs

2-2. 멤버 변수를 private으로 만들면서 코드량 줄이기

  • code 1과 code2는 결과 값이 같다. 멤버 변수를 생성자 함수에서 받을 필요 없다.
  • 단, code2의 private과 같은 접근지정자를 붙여줘야한다. (public도 가능)

  • code 1
class User {
  private firstName: string;
  private lastName: string;
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
  • code2
class User {
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
  constructor(private firstName: string, private lastName: string) {}
}

2-3. set을 이용해서 안전성 높히기

class User {
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
  private internalAge = 4;
  get age(): number {
    return this.internalAge;
  }
  set age(num: number) {
    if (num < 0) {
      throw new Error('age should be greater than 0');
    }
    this.internalAge = num;
  }
  constructor(private firstName: string, private lastName: string) {}
}
const user = new User('Steve', 'Jobs');
user.age = 6;
// user.age에 값을 할당할 시 setter 함수에서 포함하고 있는 로직이 실행된다.
// 만약 user.age에 0보다 작은 값을 할당하면 'age should be greater than 0' 이라는 에러가 던져진다.

0개의 댓글