All Articles

클래스 추출 및 인라인하기

클래스는 반드시 명확하게 추상화하고 소수의 주어진 역할만 처리해야한다. 하지만 실무에서는 연산도 추가하고, 데이터도 보강하면서 클래스가 점점 커진다.

하지만 하나씩 덕지덕지 붙이다 보면 메소드와 데이터가 너무 많아져 이해하기가 쉽지 않다. 이와는 반대로 의미 없어지는 클래스가 발생하는 경우가 생길 수도 있는데, 이는 인라인 하는 작업을 통해 없애버리는 방법도 있다.

클래스 추출하기

하나씩 연산작업, 데이터 보강하는 작업을 거치다 보면 클래스 하나가 너무 커지게 되는데 이럴 경우에는 분리해준다.

👉🏻👉🏻👉🏻 일부 데이터와 메소드를 따로 묶을 수 있다면 클래스를 분리하라는 신호이다.

  • 함께 변경되는 일이 많거나 서로 의존하는 데이터들도 분리한다.
  • 특정 데이터나 메소드 일부를 제거하면 어떤일이 일어나는지 자문해보면 좋다.
  • 제거해도 다른 필드나 메소드들이 논리적으로 문제가 없다면 분리할 수 있다는 뜻이다.

전화번호가 분리가능한 Person 클래스

class Person {
  get name() {
    return this._name;
  }
  set name(arg) {
    this.name = arg;
  }

  get telephoneNumber() {
    return `(${this.officeAreaCode} ${this.officeNumber}})`;
  }
  get officeAreaCode() {
    return this._officeAreaCode;
  }
  set officeAreaCode(arg) {
    this._officeAreaCode = arg;
  }
  get officeNumber() {
    return this._officeNumber;
  }
  set officeNumber(arg) {
    this._officeNumber = arg;
  }
}

위 Person 클래스에서 전화번호는 논리적으로도 독립적으로도 분리해도 문제가 없어보인다.

1단계: 먼저 빈 전화번호를 표현하는 Telephone Class를 정의한다.

class TelephoneNumber {}

2단계: Person클래스의 인스턴스를 생성할때 전화번호 인스턴스도 함께 생성해준다.

class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
}

class TelephoneNumber {
  get officeAreaCode() {
    return this._officeAreaCode;
  }

  set officeAreaCode(arg) {
    this._officeAreaCode = arg;
  }
}

3단계: 필드들을 하나씩 새 클래스로 옮긴다.

class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
  get telephoneNumber() {
    return `(${this.officeAreaCode} ${this.officeNumber}})`;
  }

  get officeAreaCode() {
    return this._telephoneNumber.officeAreaCode;
  }
  set officeAreaCode(arg) {
    this.this._telephoneNumber.officeAreaCode = arg;
  }
  get officeNumber() {
    return this._telephoneNumber.officeNumber;
  }
  set officeNumber(arg) {
    this._telephoneNumber.officeNumber = arg;
  }
}

4단계: 필드들을 옮기고 마지막으로 telephoneNumber 메소드도 옮긴다.

class TelephoneNumber {
  get telephoneNumber() {
    return `(${this.officeAreaCode} ${this.officeNumber}})`; // 전화번호를 보여주는 telephoneNumber 메소드
  }
  get officeAreaCode() {
    return this._officeAreaCode;
  }
  set officeAreaCode(arg) {
    this._officeAreaCode = arg;
  }
}
class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
  get telephoneNumber() {
    return this._telephoneNumber.telephoneNumber;
  }
  get officeAreaCode() {
    return this._telephoneNumber.officeAreaCode;
  }
  set officeAreaCode(arg) {
    this.this._telephoneNumber.officeAreaCode = arg;
  }
  get officeNumber() {
    return this._telephoneNumber.officeNumber;
  }
  set officeNumber(arg) {
    this._telephoneNumber.officeNumber = arg;
  }
}

5단계: 새로만든 클래스는 순순한 전화번호를 뜻하므로 사무실이란 (office)라는 단어를 사용할 필요가 없다. 그러니 이름들을 적절히 바꾸어준다.

class TelephoneNumber {
  get telephoneNumber() {
    return `(${this._areaCode} ${this._number}})`;
  }
  get areaCode() {
    return this._areaCode;
  }
  set areaCode(arg) {
    return (this._areaCode = arg);
  }
  get number() {
    return this._number;
  }
  set number(arg) {
    this._number = arg;
  }
}


class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
  get telephoneNumber() {
    return this._telephoneNumber.telephoneNumber;
  }
  get officeAreaCode() {
    return this._telephoneNumber.areaCode;
  }
  set officeAreaCode(arg) {
    this.this._telephoneNumber.areaCode = arg;
  }
  get officeNumber() {
    return this._telephoneNumber.number;
  }
  set officeNumber(arg) {
    this._telephoneNumber.number = arg;
  }
}

6단계: 마지막으로 전화번호를 사람이 읽기 좋은 포맷으로 출력하는 역할도 전화번호 클래스에 맡긴다.

class TelephoneNumber {
  get telephoneNumber() {
    return `(${this._areaCode} ${this._number}})`;
  }
  get areaCode() {
    return this._areaCode;
  }
  set areaCode(arg) {
    return (this._areaCode = arg);
  }

  get number() {
    return this._number;
  }
  set number(arg) {
    this._number = arg;
  }
  toString() {
    return `(${this._areaCode} ${this._number}})`;
  }
}

class Person {
  get telephoneNumber() {
    return this._telephoneNumber.toString();
  }
}



클래스 인라인하기

더 이상 제 역할을 못해서 그대로 두면 안되는 클래스는 인라인해버린다.

  • 두 클래스의 기능을 지금과 다르게 배분하고 싶을 때도 클래스를 인라인한다.
  • 클래스를 인라인해서 하나로 합친 다음 새로운 클래스를 추출하는게 더 쉬울 수도 있기 때문이다.

배송 추적 정보를 표현하는 TrackingInformation 클래스

class TrackingInformation {
  get shippingCompany() {
    return this._shippingCompany;
  }
  set shippingCompany(arg) {
    this._shippingCompany = arg;
  }
  get trackingNumber() {
    return this._trackingNumber;
  }
  set trackingNumber(arg) {
    this._trackingNumber = arg;
  }
  get display() {
    return `${this.shippingCompany}: ${this.trackingNumber}`;
  }
}

이 TrackingInformation 클래스는 Shipment 클래스의 일부처럼 사용된다.

class Shipment {
  get trackingInfo() {
    return this._trackingInformation.display;
  }
  get trackingInformation() {
    return this._trackingInformation;
  }
  set trackingInformation(aTrackingInformation) {
    this._trackingInformation = aTrackingInformation;
  }
}

예전에는 TrackingInformatino이 유용했겠지만 더 이상 제역할을 못하므로 Shipment 클래스로 인라인해버리자. 예전 사용되는 곳을 보니 아래와 같이 사용되고 있었다.

aShipment.trackingInformation.shippingCompany = request.vendor;

1단계: 외부에서 직접 호출하는 TrackingInformation의 메소드들을 Shipment로 옮긴다.

먼저 Shipment에 위임 함수를 만들고 클라이언트가 이를 호출하도록 한다.

class Shipment {
 get trackingInfo() {
   return this._trackingInformation.display;
 }
 get trackingInformation() {
   return this._trackingInformation;
 }
 set trackingInformation(aTrackingInformation) {
   this._trackingInformation = aTrackingInformation;
 }
 set shippingCompany(arg) {
   this._trackingInformation.shippingCompany = arg;  //위임용. 실제로는 get 함수도 같이 만들어야 오류가 나지 않음
 }
}

그럼 사용되는 방식이 이렇게 바뀐다.

aShipment.shippingCompany = request.vendor;

2단계: TrackingInformation의 모든 요소를 shipment로 옮긴다.

우선 보여주는 display를 인라인한다.

class Shipment {
  get trackingInfo() {
    return `${this.shippingCompany}: ${this.trackingNumber}`; //옮김
  }
  get trackingInformation() {
    return this._trackingInformation;
  }
  set trackingInformation(aTrackingInformation) {
    this._trackingInformation = aTrackingInformation;
  }
  set shippingCompany(arg) {
    this._trackingInformation.shippingCompany = arg;
  }
}

배송 회사 필드도 인라인한다.

class Shipment {
  get trackingInfo() {
    return `${this.shippingCompany}: ${this.trackingNumber}`;
  }
  get trackingInformation() {
    return this._trackingInformation;
  }
  set trackingInformation(aTrackingInformation) {
    this._trackingInformation = aTrackingInformation;
  }
  get shippingCompany() { //배송회사 필드 인라인
    this.shippingCompany;
  }
  set shippingCompany(arg) {
    this.shippingCompany = arg;
  }
}

3단계 반복후 다 옮기면 TrackingInformation 클래스를 삭제한다.

// 최종
class Shipment {
  get trackingInfo() {
    return `${this.shippingCompany}: ${this.trackingNumber}`;
  }
  get trackingNumber() {
    return this._trackingNumber;
  }
  set trackingNumber(arg) {
    this._trackingNumber = arg;
  }
  get shippingCompany() {
    this.shippingCompany;
  }
  set shippingCompany(arg) {
    this.shippingCompany = arg;
  }
}