😀 책에서 기억하고 싶은 내용을 써보세요.

class Person {
	get officeAreaCode() {return this._officeAreaCode;}
	get officeNumber() {return this._officeNumber;}
}
class Person {
	get officeAreaCode() {return this._telephoneNumber.areaCode;}
	get officeNumber() {return this._telephoneNumber.number;}
}
class TelephoneNumber {
	get areaCode() {return this._areaCode;}
	get number() {return this._number;}
}

 

배경

  • 메서드와 데이터가 너무 많은 클래스는 이해하기가 쉽지 않으니 잘 살펴보고 적절히 분리하는 것이 좋다.
  • 특히 일부 데이터와 메서드를 따로 묶을 수 있다면 어서 분리하라는 신호다.
  • 함께 변경되는 일이 많거나 서로 의존하는 데이터들도 분리한다.
  • 특정 데이터나 메서드 일부를 제거하면 어떤 일이 일어나는지 자문해보면 판단에 도움이 된다. ⇒ 제거해도 다른 필드나 메서드들이 논리적으로 문제가 없다면 분리할 수 있다는 뜻이다.

 

절차

  1. 클래스의 역할을 분리할 방법을 정한다.
  2. 분리된 역할을 담당할 클래스를 새로 만든다.
    • 원래 클래스에 남은 역할과 클래스 이름이 어울리지 않는다면 적절히 바꾼다.
  3. 원래 클래스의 생성자에서 새로운 클래스의 인스턴스를 생성하여 필드에 저장해둔다.
  4. 분리될 역할에 필요한 필드들을 새 클래스로 옮긴다(필드 옮기기). 하나씩 옮길 때마다 테스트
  5. 메서드들도 새 클래스로 옮긴다(함수 옮기기). 이때 저수준 메서드, 즉 다른 메서드를 호출하기보다는 호출을 당하는 일이 많은 메서드부터 옮긴다. 하나씩 옮길 때마다 테스트한다.
  6. 양쪽 클래스의 인터페이스를 살펴보면서 불필요한 메서드를 제거하고, 이름도 새로운 환경에 맞게 바꾼다.
  7. 새 클래스를 외부로 노출할지 정한다. 노출하려거든 새 클래스에 참조를 값으로 바꾸기를 적용할지 고민해본다.

 

예시

단순한 Person 클래스

// Person 클래스
get name()              {return this._name;}
get name(arg)           {this._name = arg;}
get telephoneNumber()   {return `(${this.officeAreaCode}) ${this.officeNumber}`;}
get officeAreaCode()    {return this._officeAreaCode;}
set officeAreaCode(arg) {return this._officeAreaCode = arg;}
get officeNumber()      {return this._officeNumber;}
set officeNumber(arg)   {this._officeNumber = arg;}

 

1️⃣ 전화번호 관련 동작을 별도 클래스로 추출

2️⃣ 먼저 빈 전화번호를 표현하는 TelephoneNumber 클래스를 정의

class TelephoneNumber {
}

 

3️⃣ 다음으로 Person 클래스의 인스턴스를 생성할 때 전화번호 인스턴스도 함께 생성해 저장

// Person 클래스
constructor() {
	this._telephoneNumber = new TelephoneNumber();
}
// TelephoneNumber 클래스
get officeAreaCode()    {return this._officeAreaCode;}
set officeAreaCode(arg) {this._officeAreaCode = arg;}

 

4️⃣ 필드들을 하나씩 새 클래스로 옮긴다.

// Person 클래스
get officeAreaCode() {return this._telephoneNumber.officeAreaCode;}
set officeAreaCode() {this._telephoneNumber.officeAreaCode = arg;}

테스트해서 문제없으면 다음 필드로 넘어간다.

 

// TelephoneNumber 클래스
get officeNumber()    {return this._officeNumber;}
set officeNumber(arg) {this._officeNumber = arg;}
// Person 클래스
get officeNumber()    {return this._telephoneNumber.officeNumber;}
set officeNumber(arg) {this._telephoneNumber.officeNumber = arg;}

 

다시 테스트해보고, 5️⃣ 이어서 telephoneNumber() 메서드를 옮긴다.

// TelephoneNumber 클래스
get telephoneNumber() {return `(${this.officeAreaCode}) ${this.officeNumber}`;}
// Person 클래스
get telephoneNumber() {return this._telephoneNumber.telephoneNumber;}

 

6️⃣ 정리 단계

  • 새로 만든 클래스는 전화번호를 뜻하므로 사무실(office)이란 단어를 쓸 이유가 없다.
  • 마찬가지로 전화번호라는 뜻도 메서드 이름에서 다시 강조할 이유가 없다.

메서드들의 이름을 적절히 바꿔준다(함수 선언 바꾸기)

// TelephoneNumber 클래스
get areaCode()    {return this._areaCode;}
set areaCode(arg) {this._areaCode = arg;}
get number()      {return this._number;}
set number(arg)   {this._number = arg;}
// Person 클래스
get officeAreaCode()    {return this._telephoneNumber.areaCode;}
set officeAreaCode(arg) {this._telephoneNumber.areaCode = arg;}
get officeNumber()      {return this._telephoneNumber.number;}
set officeNumber(arg)   {this._telephoneNumber.number = arg;}

 

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

// TelephoneNumber 클래스
toString() {return `(${this.areaCode}) ${this.number}`;}
// Person 클래스
get telephoneNumber() {return this._telephoneNumber.toString();}

 

7️⃣ 전화번호는 여러모로 쓸모가 없으니 이 클래스는 클라이언트에게 공개하는 것이 좋겠다.

그러면 “office”로 시작하는 메서드들을 없애고 TelephoneNumber의 접근자를 바로 사용하도록 바꿀 수 있다. 그런데 기왕 이렇게 쓸 거라면 전화번호를 값 객체로 만드는게 나으니 참조를 값으로 바꾸기부터 적용한다.

 

 

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.

  • 다양한 역할을 같이 수행하는 클래스는 정말 코드를 이해하기 힘들다.
  • 공통된 업무를 하는 메서드를 모아놓은 클래스는 재사용성이 정말 좋았다.
  • static으로 정의된 메서드를 보면 서로 결합성이 높은 것을 많이 봤습니다. ⇒ 그래서 회사에서 코드를 작성하면 우선 static을 최소화 하면서 사용할려고 하고 있습니다.

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 규모가 점점 커질수록 클래스에 다양한 기능을 집어넣는 경우를 보았다. 회사에서 이런 코드를 보면 어떻게 해야 할까?
    • 우선 리팩터링을 하기에는 회사에서의 승인을 받아야 하는 문제 발생
    • 회사에서 새기능을 구현해야 하는데도 시간이 부족한데 리팩터링을 할 수 있을지 고민

 

 

반응형

+ Recent posts