조합을 이용해 객체 통합하기
- 7장은 루비의 '모듈' 을 이용한 방식이라 패스
- 교향곡으로 비유
- 작곡가는 음표를 모아 음악을 작곡(compose, 조합과 동음어)하지만 음악은 음표들의 총합 이상이다.
- 교향곡은 음표들을 포함하고 있지만 그 자체가 음표들인 것은 아니다. 여기에는 그 이상의 것이 있다.
- 객체지향 조합을 이용하면 간단하고 독립적인 객체를 보다 크고 복합적인 것으로 통합할 수 있다.
- 조합에서 좀 더 큰 객체는 자신의 부분들을 가지고 있다. 즉 가지고 있는(has-a) 관계를 맺는다.
- 각 조합의 부품들을 가지고 있을 뿐 아니라 인터페이스를 통해 각 부품들과 소통한다.
- 부분이란 곧 역할이며, 부분의 합(예를 들어 자전거)은 주어진 역할을 수행하는 어떤 부품(객체)와도 즐겁게 협업할 수 있다.
자전거 부품 조합하기
- 6장에서 상속을 활용한 자전거 클래스 구현 예시를 조합의 형태로 교체하기
- 기존 코드를 무시하고 자전거가 어떻게 조합되어야 하는가를 고민해보자
Bicycle
클래스가 spares
메세지에 반응하여 예비 부품 목록을 반환한다. 자전거의 모든 부품을 들고 있는 객체를 만든다면 spares
메세지를 이 객체에 전달할 수 있을 것이다.
- 그래서 부품의 모음을 담당할
Parts
라는 클래스가 필요하다.
- 새로운 디자인을 적용하기 위해 이전 코드 대부분을 지우고
parts
변수를 추가한 뒤, spares
는 parts
에 위임한다.
class Bicycle {
constructor({ size, parts } = {}) {
this.size = size;
this.parts = parts;
}
get spares() {
return this.parts.spares;
}
}
Parts
로 상속 관계를 만들어보자
Bicycle
에서 제거되었던 부품의 행동들을 구현해야한다. 제일 먼저 Parts
의 상속관계로 만들어본다.
class Parts {
constructor(args = {}) {
this.chain = args.chain || this.defaultChain;
this.tireSize = args.tireSize || this.defaultTireSize;
this.postInitialize(args);
}
postInitialize(args) {}
get spares() {
return {
tireSize: this.tireSize,
chain: this.chain,
...this.localSpares,
};
}
get defaultTireSize() {
throw new Error("Not Implemented");
}
get defaultChain() {
return "10-speed";
}
get localSpares() {
return {};
}
}
class RoadBikeParts extends Parts {
postInitialize(args) {
this.tapeColor = args.tapeColor;
}
get localSpares() {
return {
tapeColor: this.tapeColor,
};
}
get defaultTireSize() {
return "23";
}
}
class MountainBikeParts extends Parts {
postInitialize(args) {
this.frontShock = args.frontShock;
this.rearShock = args.rearShock;
}
get localSpares() {
return {
rearShock: this.rearShock,
};
}
get defaultTireSize() {
return "2.1";
}
}
- 6장과 비교하여
Bicycle
의 상속 관계와 거의 똑같지만 약간의 차이가 있는 정도이다.
- 자전거는
RoadBikeParts
를 가지고 있든 MountainBikeParts
를 가지고 있든 자신의 size
, spares
를 알고 답할 수 있다.
const roadBike = new Bicycle({
size: "L",
parts: new RoadBikeParts({ tapeColor: "red" }),
});
console.log(roadBike.size); // => 'L'
console.log(roadBike.spares); // => { tireSize: '23', chain: '10-speed', tapeColor: 'red' }
const mountainBike = new Bicycle({
size: "L",
parts: new MountainBikeParts({ rearShock: "Fox" }),
});
console.log(mountainBike.size); // => 'L'
console.log(mountainBike.spares); // => { tireSize: '2.1', chain: '10-speed', rearShock: 'Fox' }
- 6장의 구현체 동작과 엄청난 차이가 있진 않지만 결정적인 차이를 발견할 수 있다.
Bicycle
클래스에 포함된 자전거 관련 코드는 사실 별로 없었다는 점이다.
- 위의 코드 대부분은 개별 부품을 다루고 있다. 그리고
Parts
상속 관계를 리팩터링하면 어떤 식으로 될까?
Parts 객체 조합하기
- 각각의 부품을 담당하는 클래스를 만들 예정인데, 각각의 부분에
Part
라는 이름이 붙으면서 용어의 혼란이 올 수 있다. (영어권이라 복수 및 단수 때문으로 보임)
- 위의 디자인을 적용하기 위해 새
Part
클래스를 만들어본다. Parts
는 Part
의 조합이 된다.