소트웍스 앤솔로지의 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});
최악의 경우다. name
과 position
이 public
접근 제어자로 열려있어 클래스 밖에서 마음대로 수정이 가능하고, 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});
name
과 position
의 접근제어자로 private
으로 변경했다. Car
의 name
은 객체의 생성자 외에서는 변경될 수 없게 setter
를 없애고, getter
만을 남겼다. position
의 경우는 변경이 될 수 있어야 하니 getter
와 setter
모두를 남겼다. 하지만 여전히 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});
position
의 setter
도 없애며 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
을 모두 없애고 status
의 getter
를 만들었다. 이를 통해 실제로 클래스 밖에서 훨씬 간단해진 코드로 같은 로직을 수행할 수 있는 것을 볼 수 있다.
position
을 값 객체화해서 역할을 따로 부여하는게 맞겠지만, 규칙 3: 모든 원시값과 문자열을 포장하라을 통해 따로 리팩토링해보기로 하고 여기서는 그냥 넘어간다.
Getter, Setter를 지양하며, 객체에 메세지를 보낸다. 이를 통해 객체가 더 많은 책임과 역할을 가지게 되고, 자연스럽게 각각의 객체가 추상화되고 캡슐화되는 것을 볼 수 있다.