관리 메뉴

너와 나의 스토리

[리팩토링] Replace Primitive with Object 기본형을 객체로 바꾸기(데이터 항목 -> 객체) 본문

개발/Refactoring

[리팩토링] Replace Primitive with Object 기본형을 객체로 바꾸기(데이터 항목 -> 객체)

노는게제일좋아! 2021. 10. 26. 00:46
반응형
단순한 출력 이상의 기능이 필요해지는 순간 그 데이터를 표현하는 전용 클래스를 정의하라

-> 나중에 특별한 동작이 필요해지면 이 클래스에 추가함으로써 점점 유용해진다.

 

 

절차

  1. 변수 캡슐화
  2. 단순한 값 클래스(value class) 만들기 
    • 생성자는 기존 값을 인수로 받아서 저장(setter), 이 값을 반환하는 getter 추가
  3. 정적 검사 
  4. 값 클래스의 인스턴스를 새로 만들어서 필드에 저장하도록 setter를 수정
  5. 새로 만든 클래스의 getter를 호출한 결과를 반환하도록 getter 수정
  6. 테스트
  7. 함수 이름 바꾸기 리팩터링

 

 

예시: 레코드 구조에서 데이터를 읽어 들이는 단순한 주문(Order) 클래스

  • Before:
    • Order 클래스의 우선순위(priority) 필드를 단순히 string 타입으로 사용
    • class Order { constructor(data){ this.priority = data.priority; } }
    • 클라이언트에서는 이를 다음과 같이 사용한다.
    • highPriorityCount = orders.filter( o=> "high" == o.priority 
                                          || "rush" == o.priority).length;
  • Step 1: 캡슐화
    • priority 변수 캡슐화
    • class Order {
          constructor(data) {
              this.priority = data.priority;
          }
      
          get prirority() {return this._priority;}
          set priority(aString) {this.priroriy = aString;}
      }
  • Step 2: 단순한 값 클래스(value class) 만들기.
    •  Priority 값 클래스를 생성
    • class Priority {
          constructor(value) {this._value = value;}
          toString() {return this._value;}
      }
    • 이 클래스는 표현할 값을 받는 생성자와 그 값을 문자열로 반환하는 변환 함수로 구성된다.
    • 여기서는 getter 대신 toString()을 사용하였다.
      • 클라이언트 입장에서 보면 우선순위 속성 자체를 받은 게 아니라 해당 속성을 문자열로 표현한 값을 얻는 것이기 때문이다. 
  • Step 3: 정적 검사
  • Step 4, 5: 값 클래스를 사용하도록 접근자들을 수정
    • class Order {
          Order(data) {
              this.priority = data.priority;
          }
      
          get prirority() { return this._priority.toString(); }
          set priority(aString) { this.priroriy = new Priority(aString); }
      }
  • Step 6: 테스트
  • Step 7: 함수 이름 바꾸기 리팩터링
    • getter를 보면 함수 이름이 부적절하다. 반환하는 값은 우선순위 자체가 아니라 우선순위를 표현하는 문자열이기 때문이다. 
      • 그러니 "우선순위 문자열"을 반환하는 메서드임을 나타내도록 함수 이름을 변경해주자.
    • get prirorityString() { return this._priority.toString(); }

 

 

+ 더 가다듬기

  • priority 클래스에 동작을 추가해보자.
    • 우선순위 값을 검증하고 비교하는 로직을 추가하였다.
    • 우선순위 값은 string 객체이기 때문에 값 비교를 위해 equals() 메서드를 추가하였다.
  • class Priority {
        constructor(value) {
            if (value instanceof Priority) return value;
            if (Priority.legalValues().includes(value))
                this._value = value;
            else
                throw new Error('<${value} is invalid>')
        }
        toString() { return this._value; }
        get _index() { return Priority.legalValues().findIndex(s => s === this._value); }
        static legalValues() { return ['low', 'normal', 'high', 'rush']; }
        equals(other) { return this._index === other._index; }
        higherThan(other) { return this._index > other._index; }
        lowerThan(other) { return this._index < other._index; }
    }
  • 이제 클라이언트 코드를 좀 더 의미 있게 작성할 수 있게 되었다.
  • highPriorityCount = orders.filter(o => o.priority.higherThan(new Priority("normal")))
                                            .length;

 

 

 

 

이해 안 되는 부분:

  • Priority 클래스를 Order 클래스에서만 사용하는 게 아니라 다른 곳에서도 유용하게 사용할 수 있도록 생성자를 수정하고 order 클래스에서 priority 객체를 제공하는 게터를 추가로 생성함. 
class Order {
    // ...
    get priority() { return this, _priority; }
    get prirorityString() { return this._priority.toString(); }
    set priority(aString) { this.priroriy = new Priority(aString); }
}

class Priority {
    constructor(value) {
        if (value instanceof Priority) return value;
            this._value = value;
    }
    // ...
}

-> ??? 

 

 

 

 

출처:

- [리팩터링 2판]

 

 

반응형
Comments