😀 책에서 기억하고 싶은 내용을 써보세요.
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;}
}
배경
- 메서드와 데이터가 너무 많은 클래스는 이해하기가 쉽지 않으니 잘 살펴보고 적절히 분리하는 것이 좋다.
- 특히 일부 데이터와 메서드를 따로 묶을 수 있다면 어서 분리하라는 신호다.
- 함께 변경되는 일이 많거나 서로 의존하는 데이터들도 분리한다.
- 특정 데이터나 메서드 일부를 제거하면 어떤 일이 일어나는지 자문해보면 판단에 도움이 된다. ⇒ 제거해도 다른 필드나 메서드들이 논리적으로 문제가 없다면 분리할 수 있다는 뜻이다.
절차
- 클래스의 역할을 분리할 방법을 정한다.
- 분리된 역할을 담당할 클래스를 새로 만든다.
- 원래 클래스에 남은 역할과 클래스 이름이 어울리지 않는다면 적절히 바꾼다.
- 원래 클래스의 생성자에서 새로운 클래스의 인스턴스를 생성하여 필드에 저장해둔다.
- 분리될 역할에 필요한 필드들을 새 클래스로 옮긴다(필드 옮기기). 하나씩 옮길 때마다 테스트
- 메서드들도 새 클래스로 옮긴다(함수 옮기기). 이때 저수준 메서드, 즉 다른 메서드를 호출하기보다는 호출을 당하는 일이 많은 메서드부터 옮긴다. 하나씩 옮길 때마다 테스트한다.
- 양쪽 클래스의 인터페이스를 살펴보면서 불필요한 메서드를 제거하고, 이름도 새로운 환경에 맞게 바꾼다.
- 새 클래스를 외부로 노출할지 정한다. 노출하려거든 새 클래스에 참조를 값으로 바꾸기를 적용할지 고민해본다.
예시
단순한 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을 최소화 하면서 사용할려고 하고 있습니다.
🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
- 규모가 점점 커질수록 클래스에 다양한 기능을 집어넣는 경우를 보았다. 회사에서 이런 코드를 보면 어떻게 해야 할까?
- 우선 리팩터링을 하기에는 회사에서의 승인을 받아야 하는 문제 발생
- 회사에서 새기능을 구현해야 하는데도 시간이 부족한데 리팩터링을 할 수 있을지 고민
반응형