[리펙터링 2판] 7장 - 캡슐화
도서

[리펙터링 2판] 7장 - 캡슐화

반응형

 

  7장은 캡슐화 주제로 class를 사용하는것이 대부분이다. 클래스는 객체 지향의 프로그래밍의 컨셉으로 정보를 숨기는 용도로 설계되었다고 한다. 

클래스는 내부의 정보뿐 아니라 클래스 사이의 연결 관계를 숨기는 데도 유용하다. 모듈을 분리하는 가장 중요한 기준은 모듈이 자신을 제외한 다른부분에 드러내지 않아야 할 비밀을 얼마나 잘 숨기냐에 있다.

이런 내용을 알고는 있지만 직접 와닿는 부분이 없었고, 굳이? 왜? 라는 의문문이 많이 따라 왔으나 설명과 함께 예시, 예제 코드를 보면서 그러한 부분을 많이 해소 할 수 있었다.

 

 

7.1 레코드 캡슐화 하기

내용

  • 레코드를 데이터 클래스로 전환

적용

  • 데이터 레코드는 정의하고 사용하기 간단하지만, 계산해서 얻을 수 있는 값과 그렇지 않은 값을 명확히 구분해야 하는 단점

결과

  • 클래스로 레코드를 묶는다면 Class의 Getter를 통하여 값을 구분하는데 도움
  • 이름변경 시 편리 

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;}
}

 

큰 데이터 구조를 다를수록 쓰기 데이터에 집중하고, 값을 수정하는 부분을 명확하게 드러내고 한 곳에 모아두는 일이 굉장히 중요하다.

 

책에서는 예시 두 개를 들고있는데, 하나는 간단한 레코드 캡슐화하는 내용이고 (6.6 변수를 캡슐화하기)를 제일 먼저 하면서 클래스를 사용하여 게더와 세터를 통한 변수를 캡슐화 하는 내용이다. 

 

두 번째 예시에서는 중첩된 레코드 캡슐화하기이다. 변수를 캡슐화하고, 데이터 구조 안으로 들어가는 코드를 세터로 뽑아내는 작업을 한다.

읽기 처리는 세터와 동일하게 읽는 코드를 모두 독립 함수로 추출한 다음 클래스로 옮는 방법과, 또 다른 방법은 레코드 캡슐화를 재귀적으로 하는 방법이 있다.

데이터 크기가 클 수록 복잡하기에 유동적으로 사용방법을 정해야 한다.

 

 

 

7.2 컬렉션 캡슐화하기

내용

  • 컬렉션 원본을 외부에서 직접 변경할 수 없도록 하는 리팩터링
  • 코드베이스에서 컬렉션 접근 처리 방식이 통일
  • 무분별한 getter, setter 사용이 아닌 getter할 대상의 필요한 인터페이스만 노출해야 한다.

적용

  • 컬렉션 게터가 원본 컬렉션을 반환하지 않게 만들어 클라이언트가 실수로 컬렉션을 바꿀 가능성을 차단하기 위함

결과

  • 모듈 밖에서 컬렉션이 수정되어 발생하는 찾기 어려운 오류를 예방함

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) { ... }

 

예시 코드만 봐도 무엇인가 꽁꽁싸매는 느낌이 든다. 위의 글처럼 효과를 내기 위함이다.

 

캡슐화 하면서, 컬렉션에 대해서는 불필요한 복제본을 만드는 편이 예상치 못한 수정으로 인한 오류를 디버깅 하는 것 보다 낫다.

 

 

 

7.3 기본형을 객체로 바꾸기

내용

  • 단순한 정보를 표현하는 데이터도 개발이 진행되면서 더 이상 간단하지 않게 변하게 됨
  • 데이터 값, 특정 상태를 객체로 변환
  • 객체로 바꾸면 함수를 추가하여 상태 비교등을 객체 내부로 캡슐화 가능

적용

  • 단순한 출력 이상의 기능이 필요할 때, 데이터를 표현하는 전용 클래스를 정의

결과

  • 데이터를 단순히 감싼 것 처럼 보이지만, 프로그램이 커질수록 유용하며 코드베이스에 미치는 영향이 크다.

Before

orders.filter(o => "high" === o.priority
                || "rush" === o.priority);

After

orders.filter(o => o.priority.higherThan(new Priority("normal")))

 

 

 

7.4 임시 변수를 질의 함수로 바꾸기

내용

  • 임시 변수를 사용하면 값을 계산하는 코드가 반복되는 것을 줄이고, 변수이름을 통하여 값의 의미를 설명
  • 함수로 만들어 사용하는 편이 나을 때도 있음

적용

  • 부자연스러운 의존관계나 부수효과를 찾고 제거가능
  • 클래스 안에서 적용할 때 효과가 크다.

결과

  • 계산처리를 재사용할 수 있어 코드 중복이 줄어든다.

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;

 

변수는 값을 한 번만 계산나고 그 뒤로는 읽기만 해야한다. 더 복잡한 코드 덩어리에서 여러 차례 다시 대입하는 경우 모두 질의 함수로 추출해야한다. 옛날 주소처름 스냅숏 용도로 쓰이는 변수에넌 이 리팩터링을 적용하면 안된다.

 

변수를 사용할 때마다 계산 로직이 매번 다를 결과를 내지 않는지 확인을 해야하는 것이 중요하다.

 

 

 

7.5 클래스 추출하기

내용

  • 기존 클래스에서 새로운 역할을 추가하면 굉장히 복잡해진다.

적용

  • 메서드와 데이터가 많은 클래서는 분리해야 함
  • 데이터와 메서드를 따로 묶어야할 때

결과

  • 클래스를 이해하기 편해진다.

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;}
}

 

클래스는 반드시 명확하게 추상화하고 소수의 주어진 역할만 처리해야한다.

6장에서의 함수도 길게 사용을 꺼려하는 것처럼 클래스에서도 비슷하게 적용 되는 것 같다.

 

 

 

7.6 클래스 인라인하기

내용

  • 7.5 클래스 추출하기 반대 개념

적용

  • 제 역할을 못 해서 그대로 두면 안 되는 클래스가 대상

결과

  • 불필요하게 분리된 클래스 제거

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;}

 

 

 

7.7 위임 숨기기

내용

  • 서버 객체의 필드가 가리키는 위임 객체의 메서드를 호출하려면 클라이언트는 이 위임객체를 알아야 함

적용

  • 위임 객체가 수정되더라도 클라이언트 영향은 받지 않기 위해서

결과

  • 의존성을 없앨 수 있다.

Before

manager = aPerson.department.manager;

After

manager = aPerson.manager;

class Person {
  get manager() {return this.department.manager;}

모듈화 설계를 제대로 하는 핵심은 캡슐화이며, 캡슐화는 모듈 시스템의 다른 부분에 대해 알아야 할 내용을 줄여준다.

 

 

 

7.8 중개자 제거하기

내용

  • 7.7 위임 숨기기와 반대 개념
  • 단순히 전달만 하는 위임 메서들만 계속 추가될 경우 번거로워진다

적용

  • 서버 클래스는 중개자 역할로 전락

결과

  • 위임 객체를 직접 호출하는게 낫다.

Before

manager = aPerson.manager;

class Person {
  get manager() {return this.department.manager;}

After

manager = aPerson.department.manager;

 

 

 

7.9 알고리즘 교체하기

내용

  • 알고리즘을 다른 알고리즘으로 바꿔야 할 때

적용

  • 기존 보다 간결한 알고리즘이 존재할 경우 또는 똑같은 기능을 제공하는 라이브러리를 찾을 때
  • 기능을 조금 변화하고 싶으면 통째로 가져와 적용 후 변경
  • 크고 복잡한 알고리즘은 교체하기 어려우면 먼저 메서드를 잘게 분리하고 알고리즘을 간소화

결과

  • 복잡한 코드를 간단한 코드로 교체

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)) || '';
}
반응형