규칙 4: 일급 콜렉션을 사용하라

kimhodol·2021년 2월 3일
4
post-thumbnail

소트웍스 앤솔로지Object Calisthenics 내용을 TypeScript로 적용해봤습니다.

지난 규칙과 같은 맥락에서 이어지는데, 규칙 3에서 원시값을 포장했다면 이번에는 모든 콜렉션을 일급 콜렉션(First Class Collection)으로 포장한다. TypeScript에서는 ArrayObject(또는 Map)를 class로 포장한다 생각하자. 단, 이런 일급 콜렉션 class의 인스턴수 변수로는 ArrayObject 하나만 가져야 한다. 우선 일급 콜렉션을 적용하지 않은 코드를 살펴보자.

interface Order {
  item: string;
  price: number;
}

const orders: Order[] = [
  { item: '치킨', price: 18000 },
  { item: '콜라', price: 2000 },
  { item: '치킨무', price: 1000 },
];

orders.push({ item: '피자', price: 15000 });

const total = orders
  .map((order) => order.price)
  .reduce((previous, current) => previous + current);

console.log(`총 금액: ₩${total}`);

주문에 해당하는 Order라는 인터페이스를 정의하고, Order의 배열인 orders를 만들었다. 주문을 추가하고 총 금액의 합을 구하고 이를 출력하는 로직이 들어가있다. 이런 상황에서 두 가지 요구사항이 추가되었다고 생각해보자.

  • orders는 비어있을 수 없다.
  • orders에서 Order를 삭제할 수 없다.

이 요구사항들을 지키기에 이 상태로는 쉽지 않다. validateEmpty 등의 함수를 만들어 배열의 상태를 검증할 수 있지만 pushpop 등 배열의 상태를 조작하는 행위가 일어날 때마다 검증을 해야하고, 이 경우에 pop도 해서는 안된다. 하지만 일급 콜렉션을 적용한다면, 이는 조금 쉬워진다.

class Orders {
  constructor(private orders: Order[]) {
    if (!orders.length) {
      throw Error('주문은 비어있을 수 없습니다.');
    }
  }

  get total() {
    return this.orders
      .map((order) => order.price)
      .reduce((previous, current) => previous + current);
  }

  add(item: string, price: number) {
    this.orders.push({ item, price });
  }

  printTotal() {
    console.log(`총 금액: ₩${this.total}`);
  }
}

const orders2 = new Orders([
  { item: '치킨', price: 18000 },
  { item: '콜라', price: 2000 },
  { item: '치킨무', price: 1000 },
]);

orders2.add('피자', 15000);

orders2.printTotal();

constructor에서 이전의 orders와 같은 일반 배열을 받으며, 그 배열이 비어있으면 예외를 발생한다. add의 경우 배열의 push와 다르게 상품명과 가격을 인자로 받고 이를 class 내부적으로 배열에 추가한다. private하게 orders를 가지기 때문에 원래 배열의 기능인 pop은 사용할 수 없다. 가격의 총합을 구하고 출력하는 로직도 클래스 외부로부터 알 수 없게 추상화되었다.

**추상화(abstraction)**는 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말한다. 우리가 KTX를 타고 이동할 수 있지만 KTX가 정확하게 어떻게 작동하는지 모른다. 이처럼 KTX의 내부기능을 사용자(이 경우에는 Orders를 사용하는 외부)로 하여금 자세한 기능을 몰라도 이용할 수 있게 만드는 것을 추상화라고 한다.

일급 콜렉션을 적용함을 통해 이를 통해 다음과 같은 이점들을 얻게 된다.

  1. 비즈니스에 종속적인 자료구조를 만들 수 있으며, 적당한 이름을 지어줄 수 있다.
    • 기존의 콜렉션을 이용해 새로운 자료구조를 만들 수 있다. 이렇게 Orders뿐 아니라 다른 곳에도 이런 방식을 적용할 수 있다. 예를 들어 자료구조 중 하나인 스택을 직접 구현할 때, 이렇게 class로 감싸고 top(),pop(), push(), empty() 메소드를 만든다면 내부에서는 배열로 구현되더라도 실제로는 스택처럼 사용할 수 있다.
  2. 콜렉션을 불변성이 보장된 불변객체로 사용할 수 있다.
    • private 변수를 사용하고 setter를 노출하지 않음으로써 기존 콜렉션의 메소드를 사용해 함부로 콜렉션의 내용을 조작할 수 없다.
  3. 상태와 행위를 한 곳에서 관리할 수 있다.

이렇게 일급 콜렉션을 적용한다면 같은 관심분야를 가지는 코드를 모을 수 있고, 기능을 추상화하기 용이해서 더 읽기 좋고 깔끔한 코드를 만들 수 있게 도와준다. 지난 규칙에서 의미를 가지는 원시값을 모두 포장했듯 의미나 기능이 있는 Array, Object등을 일급 콜렉션화해서 사용해보자.


참고자료

profile
🐕 세상에 나쁜 TypeScript는 없다

2개의 댓글

comment-user-thumbnail
2021년 2월 3일

엄청나게 좋은 글이네요! 정독하고 있습니다.

1개의 답글