hoon's bLog

Javascript 자바스크립트 코딩의 기술 8장 클래스로 인터페이스를 간결하게 유지하라 (2) 본문

IT/Javascript

Javascript 자바스크립트 코딩의 기술 8장 클래스로 인터페이스를 간결하게 유지하라 (2)

개발한기발자 2024. 2. 29. 09:23
반응형


8장 클래스로 인터페이스를 간결하게 유지하라

tip40. get과 set으로 인터페이스를 단순하게 만들어라

이전 팁에서 사용한 코드를 활용하여, 클래스는 아래 코드처럼 속성에 접근하여 변경도 가능하다.

class Coupon {
  constructor(price, expiration) {
    this.price = price;
    this.expiration = expiration || '2주';
  }

  getPriceText() {
    return `$ ${this.price}`;
  }

  getExpirationMessage() {
    return `이 쿠폰은 ${this.expiration} 후에 만료됩니다.`;
  }
}

const coupon = new Coupon(5);
coupon.price = '$10';
coupon.getPriceText(); // '$ $10'

export default Coupon;
  • line 16 : Coupon 인스턴스 생성
  • line 17 : line 16에서 설정된 price의 값 5의 속성을 변경하여 '$10'으로 설정!!
  • line 20 : Coupon 클래스를 모듈에서 내보내는 문장으로 이는 이 파일이 다른 파일에서 Coupon 클래스를 가져와 사용할 수 있다!
class Coupon {
  constructor(price, expiration) {
    this.price = price;
    this.expiration = expiration || '2주';
  }

  get priceText() {
    return `$ ${this.price}`;
  }

  get expirationMessage() {
    return `이 쿠폰은 ${this.expiration} 후에 만료됩니다.`;
  }
}

const coupon = new Coupon(5);
coupon.price = 10;
coupon.priceText;
// '$ 10'
coupon.expirationMessage
// "이 쿠폰은 2주 후에 만료됩니다."
  • get : JavaScript의 클래스 내에서 게터(getter) 메서드를 정의할 때 사용한다.
    getter 메서드는 클래스의 속성에 접근할 때 사용되며, 해당 속성을 읽을 때 특정한 동작을 수행하도록 정의한다
  • 따라서 get 키워드를 사용하여 정의된 priceText는 클래스의 속성처럼 동작하지만, 사실상 메서드이며, 호출하면 내부적으로 ${this.price}를 반환하는 메서드가 실행한다.
  • get을 사용할 경우 뒤에는 동작이 아니고 명사형태로 네이밍을 변경하면 아주 좋다.
  • 코딩 컨벤션으로 메서드나 함수는 동사로!! 속성은 명사로 하는것이 아주 좋다.
  • 특이한 점은 점 표기법으로 접근이 가능하다.
  • 객체의 속성에 접근하는 방식은 객체의 특정 속성을 읽거나 수정하는 데 더 적합하며, 이로써 코드가 객체 지향적인 설계를 보다 잘 반영했다.
class Coupon {
  constructor(price, expiration) {
    this.price = price;
  }

  set halfPrice(price) {
    this.price = price / 2;
  }
}

const coupon = new Coupon(5);
coupon.price; // 5
coupon.halfPrice = 20;
coupon.price; // 10
coupon.halfPrice // undefined
coupon.halfPrice = 50;
coupon.price; // 25
  • set : JavaScript의 설정자(setter)를 정의할 때 사용되는 키워드로 setter는 객체의 속성에 값을 할당할 때 실행되는 함수로, 해당 속성의 값을 설정하거나 가공하는 역할을 한다.
  • set 키워드를 사용하여 setter를 정의할 때, 해당 setter는 객체의 속성처럼 동작하지만, 실제로는 메서드!!
  • setter의 이름은 해당 속성의 이름으로 사용되며, setter가 호출될 때는 속성에 값을 할당할 때와 동일한 방식으로 호출됩니다.
  • setter는 속성 이름 뒤에 오는 등호(=) 연산자와 함께 사용
  • setter에 대응하는 getter가 없으므로 coupon.halfPrice 값이 undefined로 나온다.
  • 항상 getter와 setter는 쌍을 이루는것이 좋다. 하지만, 속성까지 같은 이름은 지양한다.(호출 스택이 무한히 쌓인다.)
  • 컨벤션에 맞춰 _(하이픈)을 사용해 비공개라는걸 알려준다.
  • 아래 코드와 같이 _price는 클래스 내에서 가교 역할로 사용하고 외부에서는 세터 price를 사용한다.
class Coupon {
  constructor(price, expiration) {
    this._price = price;
    this.expiration = expiration || '2주';
  }

  get priceText() {
    return `$${this._price}`;
  }

  get price() {
    return this._price;
  }

  set price(price) {
    const newPrice = price
      .toString()
      .replace(/[^\d]/g, ''); // 정수만 남기는 정규화식
    this._price = parseInt(newPrice, 10);
  }

  get expirationMessage() {
    return `이 쿠폰은 ${this.expiration} 후에 만료됩니다.`;
  }
}

const coupon = new Coupon(5);
coupon.price;        // 5
coupon.price = '$10';
coupon.price;        // 10
coupon.priceText;    // '$10'
  • 속성 이름을 내부적으로만 사용하고 외부에서는 접근할 수 없도록 하기 위해 _price와 같이 밑줄을 사용하여 속성을 명명한다!
  • 장점은 복잡도를 숨길 수 있다.
  • 단점은 자바스크립트의 모든 객체는 외부에서 접근이 허용되기 때문에 위의 예시처럼 실제로 은닉이 되지는 않는다. 때문에 다른 개발자가 이 클래스를 사용할때 실제로는 메서드를 호출하지만 속성을 설정한다고 생각할 수 있는 소통의 오류가 생길 수 있다.

tip41. 제너레이터로 이터러블 속성을 생성하라

  • 이터러블(Iterable) : Collection을 순회가능하게 한다.(객체의 일부를 배열로 변경하여 순회 가능)
  • Generator : 함수가 호출 되었을때 끝까지 실행하지 않고 중간에 빠져나갔다가 다시 돌아올 수 있는 함수
    • 호출될 때마다 iterator를 반환하며, iterator를 통해 함수의 실행을 제어할 수 있다.
    • Generator 함수는 funcion*() 이렇게 사용
    • next()라는 메서드는 함수의 일부를 반환(value, done가 있는 객체를 반환)
    • yield : 선언한 항목이 value, done은 남은 항목이 없다는 정보를 반환한다.(일종의 return)
function* getCairoTrilogy() {
  yield '궁전 샛길';
  yield '욕망의 궁전';
  yield '설탕 거리';
}

const trilogy = getCairoTrilogy();

trilogy.next(); // { value: '궁전 샛길', done: false }
trilogy.next(); // { value: '욕망의 궁전', done: false }
trilogy.next(); // { value: '설탕 거리', done: false }
trilogy.next(); // { value: undefined, done: true }
  • getCairoTrilogy Generator 함수는 세 개의 값을 순차적으로 반환하는데, 이 값들은 yield 키워드를 사용하여 반환
  • Generator 함수가 호출될 때마다 yield 키워드를 만나면 함수의 실행이 일시 중지되고 해당 값을 반환한다.
  • 이후에 next() 메서드가 호출되면 다음 yield 키워드로 이동하여 함수가 다시 실행한다.
  • trilogy.next()를 호출하여 Generator 함수를 실행하고, 첫 번째 yield 문인 '궁전 샛길'을 반환한다.
  • 만약 이후에 다시 trilogy.next()를 호출하면 '욕망의 궁전'을 반환하고, 그 다음에는 '설탕 거리'를 반환한다.
  • 마지막으로 호출되면 value 속성의 값은 undefined, done 속성이 true인 객체를 반환하여 Generator 함수의 실행이 종료되었음을 나타낸다.
  • Generator 함수 뿐만 아니라 iterable 속성을 사용할 때 next() 메서드가 사용 가능하다.(펼침연산자, for...of 등)
class FamilyTree {
  constructor() {
    this.family = {
      name: 'Doris',
      child: {
        name: 'Martha',
        child: {
          name: 'Dyan',
          child: {
            name: 'Bea',
          },
        },
      },
    };
  }

  getMembers() {
    const family = [];
    let node = this.family;
    while (node) {
      family.push(node.name);
      node = node.child;
    }
    return family;
  }
}

const family = new FamilyTree();
family.getMembers();
// ['Doris', 'Martha', 'Dyan', 'Bea'];
  • 위 코드는 한 가족의 가계도인데 자식이 자식을 낳고, 또 그 자식이 자식을 낳았는데 이름을 배열로 구하는 코드이다.
  • getter와 setter처럼 클래스에 단순한 인터페이스를 제공할 수 있다.
  • 복잡한 데이터 구조를 다루는 클래스를 만들때, 단순한 배열을 다루는 것처럼 데이터에 접근할 수 있게 한다.
  • 위 코드를 Generator를 사용하면, 아래와 같이 배열에 담지 않고 데이터를 바로 반환이 가능하다. 
class FamilyTree {
  constructor() {
    this.family = {
      name: 'Doris',
      child: {
        name: 'Martha',
        child: {
          name: 'Dyan',
          child: {
            name: 'Bea',
          },
        },
      },
    };
  }

  * [Symbol.iterator]() {
    let node = this.family;
    while (node) {
      yield node.name;
      node = node.child;
    }
  }
}

const family = new FamilyTree();
[...family];
// ['Doris', 'Martha', 'Dyan', 'Bea'];
  • gemtMemebers() 대신 * [Symbol.iterator]()로 변경 : 클래스의 iterable에 Generator로 연결
  • 이렇게 하면, iterator를 통해 함수의 실행을 제어할 수 있습니다.
  • [Symbol.iterator]는 while 루프를 사용하여 가계도를 따라가며 각 노드의 이름을 반환하는데, 먼저 현재 노드의 이름을 yield 키워드를 사용하여 반환하고, 다음 자식 노드로 이동합니다. 이 과정을 모든 노드가 탐색될 때까지 반복한다.
  • map 객체가 map iterator를 가지고 있는것과 비슷하다.

tip42. bind( )로 문맥 문제를 해결하라

class Validator {
  constructor() {
    this.message = '가 유효하지 않습니다.';
  }

  // 입력값 하나다 유효하지 않는 경우 메세지 반환
  setInvalidMessage(field) {
    return `${field}${this.message}`;
  }

  // 모든 메세지를 담긴 배열을 순회하면서 유효하지 않는 메세지들 반환
  setInvalidMessages(...fields) {
    return fields.map(this.setInvalidMessage);
  }
}

const validator = new Validator();
validator.setInvalidMessage('도시'); // "도시가 유효하지 않습니다."
validator.setInvalidMessages('도시'); // Uncaught TypeError: Cannot read property 'message' of undefined
  • setInvalidMessages 메서드에서 map안에 this.setInvalidMessage는 class 내부에 바인딩외어
  • setInvalidMessage을 호출하는데 여기서 this는 map() 메서드 콜백함수이므로 새로운 문맥(window)에 바인딩 된다.
  • 해결 방법은 먼저 메서드를 화살표 함수로 변경하는 방법이다.
class Validator {
  constructor() {
    this.message = '가 유효하지 않습니다.';
    this.setInvalidMessage = field => `${field}${this.message}`;
  }

  setInvalidMessages(...fields) {
    return fields.map(this.setInvalidMessage);
  }
}
  • 메서드가 많아지면 생성자함수는 너무 커진다.
  • 그래서 두번째 해결 방법으로는 명시적으로 bind()를 사용하는 방법이 있다.
function sayMessage() {
  return this.message;
}

const alert = {
  message: '위험해!',
};

const sayAlert = sayMessage.bind(alert);

sayAlert(); // '위험해!'

위에 코드인 Validator를 bind()를 사용해 보자.

class Validator {
  constructor() {
    this.message = '가 유효하지 않습니다.';
  }

  setInvalidMessage(field) {
    return `${field}${this.message}`;
  }

  setInvalidMessages(...fields) {
    return fields.map(this.setInvalidMessage.bind(this));
  }
}
  • 잘 동작하지만, 다른 곳에서 사용할때마다 this.setInvalidMessage.bind(this)를 계속 입력해야 한다.
  • 생성자 함수 화살표 함수처럼 bind() 사용이 가능하다.
  • 직관적이긴하나 역시 함수가 많아지면 생성자 함수가 비대해진다.(화살표 함수에 단점이라고 책에서 말해 놓은 상태이다.)
  • 그래서 위의 코드를 this 바인딩을 이용하여 개선해보면 아래와 같다.
class Validator {
  constructor() {
    this.message = '가 유효하지 않습니다.';
    this.setInvalidMessage = this.setInvalidMessage.bind(this);
  }

  setInvalidMessage(field) {
    return `${field}${this.message}`;
  }

  setInvalidMessages(...fields) {
    return fields.map(this.setInvalidMessage);
  }
}
  • 변경 전 코드에서는 setInvalidMessage 메서드를 사용할 때마다 bind 메서드를 호출하여 명시적으로 this를 설정해야 하지만, 위 코드에서는 이미 생성자 내에서 this를 바인딩하여 별도의 바인딩이 필요하지 않다.

Reference

자바스크립트 코딩의 기술 요약 / 정리

728x90
반응형