class Order {
get daysToShip() {
return this._warehouse.daysToShip;
}
}
class PriorityOrder extends Order {
get daysToShip() {
return this._priorityPlan.daysToShip;
}
}
리팩터링 후
class Order {
get daysToShip() {
return (this._priorityDelegate)
? this._priorityDelegate.daysToShip
: this._warehouse.daysToShip;
}
}
class PriorityOrderDelegate {
get daysToShip() {
return this._priorityPlan.daysToShip
}
}
🧷 배경
상속은 무언가가 달라져야 하는 이유가 여러 개여도 상속에서는 그중 단 하나의 이유만 선택해야 하며, 클래스들의 관계를 아주 긴밀하게 결합한다.
따라서 이 대신 위임을 사용하자. 위임은 객체 사이의 일반적인 관계이므로 상호작용에 필요한 인터페이스를 명확히 정의할 수 있다.
즉, 상속보다 결합도가 훨씬 약하다.
🧷 절차
생성자를 호출하는 곳이 많다면 생성자를 팩터리 함수로 바꾼다.
위임으로 활용할 빈 클래스를 만든다. 이 클래스의 생성자는 서브클래스에 특화된 데이터를 전부 받아야 하며, 보통은 슈퍼클래스를 가리키는 역참조도 필요하다.
위임을 저장할 필드를 슈퍼클래스에 추가한다.
서브클래스 생성 코드를 수정하여 위임 인스턴스를 생성하고 위임 필드에 대입해 초기화한다.
서브클래스의 메서드 중 위임 클래스로 이동할 것을 고른다.
함수 옮기기를 적용해 위임 클래스로 옮긴다. 원래 메서드에서 위임하는 코드는 지우지 않는다.
서브클래스 외부에도 원래 메서드를 호출하는 코드가 있다면 서브클래스에 위임 코드를 슈퍼클래스로 옮긴다. 이때 위임이 존재하는지를 검사하는 보호 코드로 감싸야 한다. 호출하는 외부 코드가 없다면 원래 메서드는 죽은 코드가 되므로 제거한다.
테스트한다.
서브클래스의 모든 메서드가 옮겨질 때까지 5~8 과정을 반복한다.
서브클래스들의 생성자를 호출하는 코드를 찾아서 슈퍼클래스의 생성자를 사용하도록 수정한다.
테스트한다.
서브클래스를 삭제한다.
🧷 예시: 서브클래스가 하나일 때
🧷 리팩터링 전
// Booking 클래스
class Booking {
constructor(show, date) {
this._show = show;
this._date = date;
}
get hasTalkback() {
return this._show.hasOwnProperty("talkback") && !this.isPeakDay;
}
get basePrice() {
let result = this._show.price;
if (this.isPeakDay) result += Math.round(result * 0.15);
return result;
}
}
// PremiumBooking 클래스(Booking을 상속함)
class PremiumBooking extends Booking {
constructor(show, date, extras) {
super(show, date);
this._extras = extras;
}
get hasTalkback() {
return this._show.hasOwnProperty("talkback");
}
get basePrice() {
return Math.round(super.basePrice + this._extras.premiumFee);
}
get hasDinner() {
return this._extras.hasOwnProperty("dinner") && !this.isPeakDay;
}
}
📍서브클래스를 왜 위임으로 바꾸려할까?
상속은 한 번만 사용할 있는 도구이다. 상속을 사용해야 할 다른 이유가 생긴다면, 서브클래스를 위임으로 바꾸는 게 좋다.
(프리미엄 예약을 상속이 아닌 다른 방식으로 표현하는 것이 더 좋다.)
🧷 리팩터링 후
// Booking 클래스
class Booking {
constructor(show, date) {
this._show = show;
this._date = date;
}
get hasTalkback() {
return (this._premiumDelegate)
? this._premiumDelegete.hasTalkback
: this._show.hasOwnProperty('talkback') && !this.isPeakDay;
}
get basePrice() {
let result = this._show.price;
if (this.isPeakDay) result += Math.round(result * 0.15);
return (this._premiumDelegate)
? this._premiumDelegate.extendBasePrice(result);
: result;
}
get hasDinner() {
return (this._premiumDelegate)
? this._premiumDelegate.hasDinner
: undefined;
}
_bePremium(extras) {
this._premiumDelegate = new PremiumBookingDelegate(this, extras);
}
}
// 1. 생성자를 팩터리 함수로 바꿔서 생성자 호출 부분을 캡슐화 한다.
function createBooking(show, date) {
return new Booking(show, date);
}
function createPremiumBooking(show, date, extras) {
// 3. 새로운 위임을 예약 객체와 연결한다.
const result = new Booking(show, date, extras);
result._bePremium(extras);
return result;
}
// 클라이언트(일반 예약)
aBooking = createBooking(show, date);
// 클라이언트(프리미엄 예약)
aBooking = createPremiumBooking(show, date, extras);
// 2. 새로운 위임 클래스를 만든다.
class PremiumBookingDelegate {
constructor(hostBooking, extras) {
this._host = hostBooking;
this._extras = extras;
}
get hasTalkback() {
return this._host._show.hasOwnProperty('talkback');
}
get basePrice() {
return Math.round(this._host._privateBasePrice + this._extras.premiumFee);
}
extendBasePrice(base) {
return Math.round(base + this._extras.premiumFee);
}
get hasDinner() {
return this._extras.hasOwnProperty('dinner') && !this.isPeakDay;
}
}