[Refactoring] # 레코드 캡슐화하기

mechaniccoder·2021년 10월 5일
0
post-thumbnail

레코드의 단점은 필드를 명확하게 구분해서 저장해하는 단점이 있다. 즉, 이름과 나이를 저장할때 명시적으로 "이름", "나이" 라는 key값을 정확하게 써줘야 한다. 이 레코드를 사용하는 클라이언트에서는 이 데이터를 사용하기 위해 key값이 어떻게 선언되어 있는지 직접 확인해야만 한다.

하지만 이를 클래스를 사용해 리팩토링 한다면, 어떤게 저장된 데이터고, 계산된 값인지 구분할 필요가 없어진다. 예를 들어, lastName을 가져오고 싶다면 getter를 활용하거나 메서드를 사용하면 된다. 클라이언트 입장에서는 이게 인자로 넘겨 저장된 데이터인지, 계산된 값인지 구체적인 구현들을 알 필요가 없어지는 것이다.

Code

const organization = {
  name: "유승환",
  country: "KR",
};

// 1. 먼저 위의 레코드를 캡슐화하자.
function getRawDataOfOrganization() {
  return organization;
}

//  2. 레코드를 클래스로 바꿔보자.
class Organization {
  constructor(data) {
    this._data = data;
  }
}

const organization = new Organization({ name: "유승환", country: "KR" });
function getRawDataOfOrganization() {
  return organization._data;
}
function getOrganization() {
  return organization;
}

class Organization {
  constructor(data) {
    this._data = data;
  }

  // 3. 레코드를 업데이트하는 코드는 setter를 활용한다.
  set name(aString) {
    this._data.name = aString;
  }

  // 레코드를 읽는 코드도 getter를 활용한다.
  get name() {
    return this._data.name;
  }
}

// 4. _data 필드를 펼쳐놓자.
class Organization {
  constructor(data) {
    // 이렇게 하는 장점은 원본 레코드의 참조값의 연결을 끊을 수가 있다.
    this._name = data.name;
    this._country = data.country;
  }

  set name(aString) {
    this._data.name = aString;
  }

  get name() {
    return this._data.name;
  }
  set country(aCountry) {
    this._data.coutnry = aCountry;
  }

  get country() {
    return this._data.country;
  }
}

/**
 *  중첩된 레코드를 캡슐화하는 방법을 살펴보자.
 * */

const user = {
  1994: {
    name: "seunghwan",
    id: "1994",
    usages: {
      2020: {
        1: 50,
        2: 55,
      },
      2021: {
        1: 70,
        2: 63,
      },
    },
  },
  1997: {
    name: "pauler",
    id: "1994",
    usages: {
      2020: {
        1: 50,
        2: 55,
      },
      2021: {
        1: 70,
        2: 63,
      },
    },
  },
};

// depth가 깊은 데이터를 다루기 위해서 다음과 같이 접근해야 한다.
user[customerID].usages[year][month] = amount;
function compareUsage(customerID, laterYear, month) {
  const later = user[customerID].usages[laterYear][month];
  const earlier = user[customerID].usages[laterYear - 1][month];
  return { laterAmount: later, change: later - earlier };
}

function getRawDataOfUser() {
  return user._data;
}
function setRawDataOfUser(arg) {
  user = arg;
}

// 데이터를 표현하는 클래스를 정의하자.
class UserData {
  constructor(data) {
    this._data = data;
  }

  setUsage(customerID, year, month, amount) {
    this._data[customerID].usages[year][month] = amount;
  }

  usage(customerID, year, month) {
    return this._data[customerID].usages[year][month];
  }

  get rawData() {
    return _.cloneDeep(this._data);
  }
}

function setRawDataOfUser(arg) {
  user = new UserData(arg);
}

// 이렇게 정의하면 사용하는 쪽에서 훨씬 더 가독성이 좋다.
getRawDataOfUser().setUsage(customerID, year, month, amount);

느낀점

데이터와 연관된 행동을 하거나, 중첩된 데이터를 읽고, 쓸때의 구체적인 구현은 클래스의 내부로 캡슐화를 해봤다. 항상 느끼는 거지만, api를 호출하는 클라이언트 입장에서는 최소한의 정보만을 알고 객체에게 public한 메세지만을 보내는 방식이 유지 보수에도 많은 도움이 될 것 같다고 생각한다.

profile
세계 최고 수준을 향해 달려가는 개발자입니다.

0개의 댓글