<aside> 💡 참고: 타입스크립트는 기본적으로 구조적 타이핑을 지원하기 때문에 엄밀히 따지면 오리 타입을 지원한다고 할 수 있다. 오리 타입을 활용하면 컴파일러가 철저하게 타입을 검사하는 것을 상쇄한다고 볼 수도 있으나, 상황에 따라 유연한 인터페이스를 구성하는데 사용할 수 있을 것이다. (일단 예제는 기존처럼 JS 로 작성)
</aside>
class Trip {
constructor({ bicycles, customers, vehicle }) {
this.bicycles = bicycles;
this.customers = customers;
this.vehicle = vehicle;
}
// ...
prepare(mechanic) {
mechanic.prepareBicycles(this.bicycles);
}
// ...
}
class Mechanic {
prepareBicycles(bicycles) {
bicycles.forEach((bicycle) => this.prepareBicycle(bicycle));
}
prepareBicycle(bicycle) {
// ...
}
}
Mechanic
클래스의 인스턴스여야만 Trip#prepare
를 실행할 수 있는 상황에 가깝다. 그런데 prepare
메서드를 실행할 때 각자의 준비를 할 수 있는 객체들이 늘어나는 경우라면?class Trip {
constructor({ bicycles, customers, vehicle }) {
this.bicycles = bicycles;
this.customers = customers;
this.vehicle = vehicle;
}
// ...
prepare(preparers) {
preparers.forEach((preparer) => {
if (preparer instanceof Mechanic) {
preparer.prepareBicycles(this.bicycles);
} else if (preparer instanceof TripCoordinator) {
preparer.buyFood(this.customers);
} else if (preparer instanceof Driver) {
preparer.gasUp(this.vehicle);
preparer.fillWaterTank(this.vehicle);
}
})
}
// ...
}
class TripCoordinator {
buyFood(customers) {
//...
}
}
class Driver {
gasUp(vehicle) {
// ...
}
fillWaterTank(vehicle) {
// ...
}
}
instnaceof
를 사용하여 클래스를 구분하여 올바른 메세지를 전송하고 있는데, 이 경우 의존성이 어떻게 엮이게 되는지 보일 것이다.Trip#prepare
메서드는 '하나의 목적' 을 갖고 있기 때문에 그 인자 역시 목적을 이루기 위해 협업하는 객체라는 것이다.prepare
메서드가 믿기만 하면 디자인은 훨씬 간단해질 것이다.class Trip {
constructor({ bicycles, customers, vehicle }) {
this.bicycles = bicycles;
this.customers = customers;
this.vehicle = vehicle;
}
// ...
prepare(preparers) {
preparers.forEach((preparer) => {
preparer.prepareTrip(this);
});
}
// ...
}
class Mechanic {
prepareTrip(trip) {
trip.bicycles.forEach((bicycle) => this.prepareBicycle(bicycle));
}
prepareBicycle(bicycle) {
// ...
}
}
class TripCoordinator {
prepareTrip(trip) {
this.buyFood(trip.customers);
}
buyFood(customers) {
//...
}
}
class Driver {
prepareTrip(trip) {
const vehicle = trip.vehicle;
this.gasUp(vehicle);
this.fillWaterTank(vehicle);
}
gasUp(vehicle) {
// ...
}
fillWaterTank(vehicle) {
// ...
}
}
prepareTrip
을 구현하고 있는 객체가 Preparer 이다. 반대로 이야기하면 Preparer 와 협업하는 객체는 이 객체들이 Preparer 의 퍼블릭 인터페이슬르 구현하고 있다고 믿어야 한다. 이 추상화를 이해하고 나면 코드를 수정하는 것은 매우 수비다.
instanceof
등으로 (ES6 클래스에 적용되는 어떤 속성이 있었는데 기억이 나지 않는다. 아는 분은 제보 부탁드립니다.) 인스턴스의 타입을 검사하고 있다면 오리 타입이 숨겨져 있다는 사실을 알려준다.// 예시 1
if (preparer instanceof Mechanic) {
preparer.prepareBicycles(this.bicycles);
} else if (preparer instanceof TripCoordinator) {
preparer.buyFood(this.customers);
} else if (preparer instanceof Driver) {
preparer.gasUp(this.vehicle);
preparer.fillWaterTank(this.vehicle);
}
// 예시 2
if (preparer.hasOwnProperty('prepareBicycles')) {
// ...
} else if (preparer.hasOwnProperty('buyFood')) {
// ...
}
// ...