규칙 9: Getter, Setter를 사용하지 마라

kimhodol·2021년 2월 6일
3
post-thumbnail

소트웍스 앤솔로지Object Calisthenics 내용을 TypeScript로 적용해봤습니다.

처음에 이 규칙을 접했을 때 꽤나 충격적이었다. 다른 규칙들은 할 수 있겠다라는 생각이 들었는데 이 규칙 하나로 약 2주를 고민했던 것 같다. ~~못난 내 머리 반성해라~~

개인적으로 객체지향 프로그래밍 입문에 있어 제일 핵심이 되는 주제라 생각한다. 각각의 객체가 고유 역할을 가지고 있다면, 그 객체의 속성을 직접 가져오거나 직접 변경할 일이 없어야 한다. 이 규칙은 객체에 묻지 말고 메세지를 보내라는 표현으로도 사용된다.

class Car {
  constructor(public name: string, public position: number) {}
}

const car: Car = new Car('Model X', 0);
if (Math.random() < 0.5) {
  car.position++;
}
console.log(`${car.name}: ${'*'.repeat(car.position});

최악의 경우다. namepositionpublic 접근 제어자로 열려있어 클래스 밖에서 마음대로 수정이 가능하고, Car 자체에 의미 있는 로직을 담을 수 없다.

class Car {
  constructor(private _name: string, private _position: number) {}

  get name() {
    return this._name;
  }

  get position() {
    return this._position;
  }

  set position(value: number) {
    this._position = value;
  }
}

const car: Car = new Car('Model X', 0);
if (Math.random() < 0.5) {
  car.position(car.position + 1);
}
console.log(`${car.name}: ${'*'.repeat(car.position});

nameposition의 접근제어자로 private으로 변경했다. Carname은 객체의 생성자 외에서는 변경될 수 없게 setter를 없애고, getter만을 남겼다. position의 경우는 변경이 될 수 있어야 하니 gettersetter 모두를 남겼다. 하지만 여전히 Car을 움직이는 로직이나 정보를 출력하는 로직은 클래스 밖에서 대부분 이뤄진다.

class Car {
  constructor(private _name: string, private _position: number) {}

  get name() {
    return this._name;
  }

  get position() {
    return this._position;
  }

  moveForward() {
    if (Math.random() < 0.5) {
      this._position++;
    }
  }
}

const car: Car = new Car('Model X', 0);
car.moveForward();
console.log(`${car.name}: ${'*'.repeat(car.position});

positionsetter도 없애며 Car가 움직이는 로직을 클래스의 메소드로 옮겨주었다. 이렇게 클래스 외부에서는 Car이 어떻게 움직이는지 알 수도 없게 되었다. Car이 스스로 움직일 수 있게 되었으니 추상화되고 캡슐화된 객체를 사용할 수 있게 되었다. 하지만 아직 getter가 남아있으니 이를 마지막으로 바꿔보자.

class Car {
  constructor(private _name: string, private _position: number) {}

  get status() {
    return `${this._name}: ${'*'.repeat(this._position)}`;
  }

  moveForward() {
    if (Math.random() < 0.5) {
      this._position++;
    }
  }
}

const car: Car = new Car('Model X', 0);
car.moveForward();
console.log(car.status);

클래스 내부에서 조합해서 주도록 기존 getter을 모두 없애고 statusgetter를 만들었다. 이를 통해 실제로 클래스 밖에서 훨씬 간단해진 코드로 같은 로직을 수행할 수 있는 것을 볼 수 있다.

position을 값 객체화해서 역할을 따로 부여하는게 맞겠지만, 규칙 3: 모든 원시값과 문자열을 포장하라을 통해 따로 리팩토링해보기로 하고 여기서는 그냥 넘어간다.

Getter, Setter를 지양하며, 객체에 메세지를 보낸다. 이를 통해 객체가 더 많은 책임과 역할을 가지게 되고, 자연스럽게 각각의 객체가 추상화되고 캡슐화되는 것을 볼 수 있다.

profile
🐕 세상에 나쁜 TypeScript는 없다

0개의 댓글