# Flyweight Pattern {{tag>Flyweight Pattern clojure clj}} 한 법무 법인의 시스템 관리자 크리스토펴 매튼 & 파트(Cristopher, Matton & Pharts)는 보고 시스템이 메모리를 많이 소모해서 가비지 컬렉터가 끊임없이 시스템을 몇 초 동안 멈추게 한다는 것을 발견했다. 그것을 고쳐야 한다. 페드로: 저도 전에 이런 문제를 본 적이 있어요. 이브: 무엇이 잘못된 거죠? 페드로: 많은 점(point) 데이터를 사용하는 실시간 차트 프로그램 때문이예요. 메모리를 많이 소모하거든요. 그래서 가비지 컬렉터가 시스템을 멈추게 하는 거고요. 이브: 음, 그럼 무엇을 해야 하죠? 페드로: 달리 할 수 있는 일이 별로 없어요. 캐싱도 별 도움이 안되고…​ 이브:잠깐만요! 페드로: 무슨 일이죠? 이브: 점들은 나이 데이터로 이루어져 있네요. 일반적인 나이(예를 들면, 나이 [0, 100])의 경우, 왜 미리 계산해 놓지 않는 거죠? 페드로: 플라이웨이트 패턴을 사용하라는 말씀인가요? 이브: 제 말은 객체를 재사용하자는 겁니다. class Point { int x; int y; /* some other properties*/ // precompute 10000 point values at class loading time private static Point[][] CACHED; static { CACHED = new Point[100][]; for (int i = 0; i < 100; i++) { CACHED[i] = new Point[100]; for (int j = 0; j < 100; j++) { CACHED[i][j] = new Point(i, j); } } } Point(int x, int y) { this.x = x; this.y = y; } static Point makePoint(int x, int y) { if (x >= 0 && x < 100 && y >= 0 && y < 100) { return CACHED[x][y]; } else { return new Point(x, y); } } } 페드로: 이 패턴의 경우에는 두 가지가 필요해요. 프로그램 시작 시점에 대부분의 점 데이터를 미리 계산하는 것과, 생성자 대신에 정적 팩토리 메소드를 이용해서 캐쉬된 객체를 반환하는 것이죠. 이브: 테스트해 보았나요? 페드로: 물론이죠. 시스템은 아주 정확히 동작했습니다. 이브: 좋아요, 다음은 클로저 버전이예요. (defn make-point [x y] [x y {:some "Important Properties"}]) (def CACHE (let [cache-keys (for [i (range 100) j (range 100)] [i j])] (zipmap cache-keys (map #(apply make-point %) cache-keys)))) (defn make-point-cached [x y] (let [result (get CACHE [x y])] (if result result (make-point x y)))) 이브: 이차원 배열 대신에, [x y] 좌표를 키로 갖는 일차원 맵을 만듭니다. 페드로: 자바의 경우와 마찬가지네요. 이브; 아니예요, 훨씬 더 유연해요. 세 개의 점이나 정수가 아닌 값을 캐시해야 할 필요가 있을 때에는 이차원 배열을 사용할 수 없으니까요. 페드로: 아, 이해했어요. 이브: 더 좋은 것은, 클로저에서는 [[memoize]] 함수를 이용하면 팩토리 함수 make-point를 호출한 결과를 캐싱할 수 있어요. (def make-point-memoize (memoize make-point)) 이브: 그래서 최초의 호출할 때를 제외하고는, 같은 인자를 사용해 함수를 호출하게 되면 캐시에 저장된 값을 반환해요. 페드로: 그것 참 멋진 기능이네요! 이브: 물론이죠. 하지만, 부수 효과(side effects)를 가진 함수인 경우에는 memoize 기능을 사용하지 않는 것이 좋다는 점도 기억해 두세요.