Dart의 built_collection: 불변 컬렉션을 위한 패키지

Dart의 built_collection: 불변 컬렉션을 위한 패키지

한 달 전에 저는 Dart Developer Summit에서 source_gen을 이용한 **객체 모델(object model)**에 대해 발표했어요. 거기서 자세히 볼 가치가 충분히 있는 패키지와 기법 몇 가지를 언급했는데, 이제 그 전체 이야기를 해드릴게요.

이 글은 David Morgan님이 2016년 11월 24일에 작성한 글로, 읽는 데 약 3분 정도 걸려요.

출처: Dart's built_collection for Immutable Collections

본문

먼저 소개할 건 built_collection 패키지예요.

built_collection 패키지는 **불변성(immutability)**을 갖춘 SDK스러운 컬렉션을 제공해요. 불변성이 가져다주는 건 두 가지예요.

  • 단순함과 편의성. 불변 컬렉션은 누가 수정할지 걱정할 필요 없이 여기저기로 전달할 수 있어요.
  • 성능. 가변성(mutability)은 방어적 복사(defensive copying)나 변경 감지(change detection) 같은 비싼 패턴을 만들어내요.

그런데 built_collection편리하기를 의도한다는 사실이 API에 몇 가지 도전 과제를 만들기도 해요. 직접 살펴볼게요. 먼저 불변 리스트부터요:

var list = new BuiltList<int>([1, 2, 3]);

이제 여기에 값을 하나 더하고 싶다고 해볼게요. 물론 불변이라서 그럴 수는 없어요. 사실 제가 원하는 건 새로운 리스트를 만들어서 맨 끝에 값을 하나 붙인 것이에요.

바로 여기서 이 패키지 이름의 유래가 된 **빌더 패턴(builder pattern)**을 만나게 돼요. 새 리스트를 얻으려면 빌더로 변환하고, 수정하고, 다시 빌드하면 돼요:

var builder = list.toBuilder();
builder.add(4);
var newList = builder.build();

빌더는 보통 인라인으로 쓰는데, 그렇게 해볼게요:

var newList = (list.toBuilder()..add(4)).build()

보통 빌더 패턴은 여기까지예요. 그런데 Dart에는 람다(lambda)가 있으니 더 나은 방법이 있어요:

var newList = list.rebuild((b) => b.add(4));

알아보니 Dart의 **메서드 연쇄(method cascading)**가 빌더 패턴을 아주 강력하게 만들어줘요. 거의 언제나 인라인으로 원하는 작업을 할 수 있어요:

var newList = list.rebuild((b) => b
    ..add(4)
    ..addAll([7, 6, 5])
    ..sort()
    ..remove(1));

기대했던 대로, built_collection은 참 편리하죠.

이번엔 성능 얘기를 해볼게요.

Built Collections는 현재 업데이트/발행(update/publish) 사이클이 있는 코드에 최적화되어 있어요. 즉, 컬렉션을 빌더로 변환해서 업데이트하고, 빌드한 뒤, 나머지 코드가 소비할 수 있도록 참조를 넘겨 발행한다는 뜻이에요.

이 흐름은 웹 애플리케이션, 예를 들어 Angular 앱 같은 데서 쓰이는 전형적인 패턴과 잘 맞아요. 그런 곳에서 사용자 상호작용이나 RPC 응답이 업데이트를 촉발해요. 데이터 모델은 렌더링을 위해 발행되기 전에 다시 빌드되죠.

Built Collections는 아직 매우 잦은 재빌드에는 최적화되어 있지 않아요. 그러려면 신중하게 고른 자료구조 위에 구현해야 해요. 이런 사용 사례가 있다면 연락 주세요. Built Collections의 설계상, 필요할 때 이게 가능해지도록 만드는 게 계획에 포함되어 있거든요.

불변성 그 자체를 넘어서, built_collection은 여러분이 좋고 올바른 코드를 쓰도록 도와줘요. 여기에는 여러 속성이 함께 작용해요:

  • Built Collections는 반드시 명시적 타입 파라미터로 생성해야 해요. BuiltList<dynamic> 같은 건 존재하지 않아요.
new BuiltList([1, 2, 3]);     // Throws an exception!
new BuiltList<int>([1, 2, 3]); // Better.
  • 값은 반드시 null이 아니어야 해요. Built Collection에는 null을 추가할 수 없어요.
new BuiltList([1, 2, null]);  // Throws an exception!
  • Built Collections는 비교 가능하고 해시 가능해요. 즉, 맵(map), 셋(set), 멀티맵에 넣을 수 있어서 데이터에 딱 맞는 컬렉션을 만들 수 있어요.

이런 속성은 특히 비즈니스 객체 컬렉션에 잘 어울려요. 예를 들어 쇼핑 웹 애플리케이션에서 사용자 장바구니의 현재 내용을 담는 용도로요.

이상이 built_collection이 무엇을 제공하고 왜 그런지에 대한 빠른 개요였어요. 더 자세한 정보는 github 페이지에 있어요. 다만 불변 컬렉션을 제대로 활용하려면 불변 클래스가 필요해요. 다음 글에서는 built_value를 다룰게요!

더 알아보기