Chapter07. 캡슐화

김신영·2023년 7월 15일
0

Refactoring

목록 보기
7/12
post-thumbnail

레코드 캡슐화하기 (Encapsulate Record)

Encapsulate Record

image

절차

  1. 레코드를 담은 변수를 캡슐화한다. (Encapsulate Variable)
  2. 레코드를 감싼 단순한 클래스로 해당 변수의 내용을 교체한다.
    • 이 클래스에 원본 레코드를 반환하는 접근자도 정의하고
    • 변수를 캡슐화하는 함수들이 이 접근자를 사용하도록 수정한다.
  3. 테스트한다
  4. 원본 레코드 대신 새로 정의한 클래스 타입의 객체를 반환하는 함수들을 새로 만든다.
  5. 레코드를 반환하는 예전 함수를 사용하는 코드를 4번에서 만든 새 함수를 사용하도록 바꾼다.
    • 필드에 접근할 때는 객체의 접근자를 사용한다.
    • 적절한 접근자가 없다면 추가한다.
    • 테스트한다
  6. 클래스에서 원본 데이터를 반환하는 접근자와 원본 레코드를 반환하는 함수들을 제거한다.
  7. 테스트한다.
  8. 레코드의 필드도 데이터 구조인 중첩 구조라면
    • 레코드 캡슐화하기 , 컬렉션 캡슐화하기 를 재귀적으로 적용한다.

참조

bliki: ListAndHash

Refactoring Code to Load a Document

예시

  • Before
    organization = {name: "Acme Gooseberries", country: "GB"};
  • After
    class Organization {
      constructor(data) {
        this._name = data.name;
        this._country = data.country;
      }
      get name()    {return this._name;}
      set name(arg) {this._name = arg;}
      get country()    {return this._country;}
      set country(arg) {this._country = arg;}
    }

컬렉션 캡슐화하기 (Encapsulate Collection)

Encapsulate Collection

image

절차

  1. 아직 컬렉션을 캡슐화하지 않았다면, 변수 캡슐화하기 부터 한다.
  2. 컬렉션에 원소를 추가/제거하는 함수를 추가한다.
  3. 정적 검사를 수행한다.
  4. 컬렉션을 참조하는 부분을 모두 찾는다.
    • 컬렉션의 변경자를 호출하는 코드가 모두 앞에서 추가한 추가/제거 함수를 호출하도록 수정한다.
    • 테스트한다.
  5. 컬렉션 게터를 수정해서 원본 내용을 수정할 수 없는 읽기전용 Proxy나 복제본을 반환하게 한다.
  6. 테스트한다.

참고

Collection Pipeline

예시

  • Before
    class Person {              
      get courses() {return this._courses;}
      set courses(aList) {this._courses = aList;}
  • After
    class Person {
      get courses() {return this._courses.slice();}
      addCourse(aCourse)    { ... }
      removeCourse(aCourse) { ... }

기본형을 객체로 바꾸기 (Replace Primitive with Object)

Replace Primitive with Object

image

절차

  1. 아직 변수를 캡슐화하지 않았다면 캡슐화한다. (변수 캡슐화 )
  2. 단순한 값 클래스를 만든다.
    • 생성자는 기존 값을 인수로 받아서 저장하고, 이 값을 반환하는 Getter를 추가한다.
  3. 정적 검사를 수행한다.
  4. 값 클래스의 인스턴스를 새로 만들어서 필드에 저장하도록 Setter를 수정한다.
  5. 새로 만든 클래스의 Getter를 호출한 결과를 반환하도록 수정한다.
  6. 테스트한다.
  7. 함수 이름을 바꾸면 원본 접근자의 동작을 더 잘 드러낼 수 있는지 검토한다.

예시

  • Before
    orders.filter(o => "high" === o.priority
                    || "rush" === o.priority);
  • After
    orders.filter(o => o.priority.higherThan(new Priority("normal")))

임시 변수를 질의 함수로 바꾸기 (Replace Temp with Query)

Replace Temp with Query

image

절차

  1. 변수가 사용되기 전에 값이 확실히 결정되는지, 변수를 사용할 때마다 계산 로직이 매번 다른 결과를 내지는 않는지 확인한다.
  2. 읽기전용으로 만들 수 있는 변수는 읽기전용으로 만든다.
  3. 테스트한다.
  4. 변수 대입문을 함수로 추출한다.
  5. 테스트한다.
  6. 변수 인라인하기 로 임시 변수를 제거한다.

예시

  • Before
    const basePrice = this._quantity * this._itemPrice;
    if (basePrice > 1000)
      return basePrice * 0.95;
    else
      return basePrice * 0.98;
  • After
    get basePrice() {this._quantity * this._itemPrice;}
    
    ...
    
    if (this.basePrice > 1000)
      return this.basePrice * 0.95;
    else
      return this.basePrice * 0.98;

클래스 추출하기 (Extract Class)

Extract Class

image

반대 리팩터링: 클래스 인라인하기

절차

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

예시

  • Before
    class Person {
      get officeAreaCode() {return this._officeAreaCode;}
      get officeNumber()   {return this._officeNumber;}
  • After
    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;}
    }

클래스 인라인하기 (Inline Class)

Inline Class

image

반대 리팩터링: 클래스 추출하기

절차

  1. Source 클래스의 각 public 메서드에 대응하는 메서드들을 Target 클래스에 생성한다.
  2. Source 클래스의 메서드를 사용하는 코드를 모두 Target 클래스의 위임 메서드를 사용하도록 바꾼다.
    • 테스트한다.
  3. Source 클래스의 메서드와 필드를 모두 Target 클래스로 옮긴다.
    • 테스트한다.
  4. Source 클래스를 삭제한다.

예시

  • Before
    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;}
    }
  • After
    class Person {
      get officeAreaCode() {return this._officeAreaCode;}
      get officeNumber()   {return this._officeNumber;}

위임 숨기기 (Hide Delegate)

Hide Delegate

image

반대 리팩터링: 중개자 제거하기

절차

  1. 위임 객체의 각 메서드에 해당하는 위임 메서드를 서버에 생성한다.
  2. 클라이언트가 위임 객체 대신 서버를 호출하도록 수정한다.
    • 테스트한다.
  3. 모두 수정했다면, 서버로부터 위임 객체를 얻는 접근자를 제거한다.
  4. 테스트한다.

예시

  • Before
    manager = aPerson.department.manager;
  • After
    manager = aPerson.manager;
    
    class Person {
      get manager() {return this.department.manager;}

중개자 제거하기 (Remove Middle Man)

Remove Middle Man

image

  • 클라이언트가 위임 객체의 또 다른 기능을 사용하고 싶을 경우
    • 서버에 위임 메서드를 추가해야하는데…
    • 계속 추가하다보면, 결국 서버 클래스는 중개자(Middle Man) 역할로 전락한다.
  • 이럴 경우, 클라이언트가 위임 객체를 직접 호출하는게 나을 수 있다.

반대 리팩터링: 위임 숨기기

절차

  1. 위임 객체를 얻는 Getter를 만든다.
  2. 위임 메서드를 호출하는 클라이언트가 모두 이 Getter를 거치도록 수정한다.
    • 테스트한다.
  3. 모두 수정했다면, 위임 메서드를 삭제한다.
    • 위임 필드를 캡슐화한 다음, 이를 사용하는 모든 메서드를 인라인한다.

예시

  • Before
    manager = aPerson.manager;
    
    class Person {
      get manager() {return this.department.manager;}
  • After
    manager = aPerson.department.manager;

알고리즘 교체하기 (Substitute Algorithm)

Substitute Algorithm

image

절차

  1. 교체할 코드를 함수 하나에 모은다.
  2. 이 함수만을 이용해 동작을 검증하는 테스트를 마련한다.
  3. 대체할 알고리즘을 준비한다.
  4. 정적 검사를 수행한다.
  5. 기존 알고리즘과 새 알고리즘의 결과를 비교하는 테스트를 수행한다.

예시

  • Before
    function foundPerson(people) {
      for(let i = 0; i < people.length; i++) {
        if (people[i] === "Don") {
          return "Don";
        }
        if (people[i] === "John") {
          return "John";
        }
        if (people[i] === "Kent") {
          return "Kent";
        }
      }
      return "";
    }
  • After
    function foundPerson(people) {
      const candidates = ["Don", "John", "Kent"];
      return people.find(p => candidates.includes(p)) || '';
    }
profile
Hello velog!

0개의 댓글