Composite Pattern
여배우인 벨라 호크(Bella Hock)가 소셜 네트워크에서 사용자 아바타를 보지 못하고 있다.
“모든 것이 검은 색이예요. 이거 블랙홀인가요?”
페드로: 이거 검정 사각형이네요.
이브: 흠, 이쪽도 같은 문제가 있어요.
페드로: 마지막에 추가된 기능이 사용자 아바타에 버그를 만든 것 같네요.
이브: 이상하네요, 아바타는 다른 요소들과 같은 방식으로 랜더링해요. 하지만 아바타는 눈에 보이는 거죠.
페드로: 같은 방식으로 랜더링하는 거 확실해요?
이브: 글쎄요… 아니요.
코드를 파 본다.
페드로: 도대체 이 코드들이 뭘 하는 거죠?
이브: 누군가 복사해서 붙여넣기를 했네요. 하지만 아바타에 변경 사항을 반영하는 것을 잊었어요.
페드로: 이 코드를 누가 작성했는지 확인해 보죠, git-blame
이브: 확인하는 것도 좋긴 한데, 이 문제를 고칠 필요가 있어요.
페드로: 여기에 한 줄 추가하면 간단하죠.
이브: 제 말은, 진짜 문제를 풀자는 거에요. 같은 블럭들을 처리하는데, 비슷한 코드 2개가 왜 필요하죠?
페드로: 맞네요. 합성 패턴을 사용해서 전체 페이지 랜더링을 처리할 수 있을 것 같아요. 랜더링하는 가장 작은 요소는 블럭이구요.
public interface Block { void addBlock(Block b); List<Block> getChildren(); void render(); }
페드로: 당연히 블럭은 다른 블럭을 포함할 수 있어요. 그게 바로 합성 패턴이죠. 구체적인 블럭 몇 개를 만들어 보죠.
public class Page implements Block { } public class Header implements Block { } public class Body implements Block { } public class HeaderTitle implements Block { } public class UserAvatar implements Block { }
페드로: 그리고 모든 구체적인 요소들을 Block인 것처럼 다룰 수 있죠.
Block page = new Page(); Block header = new Header(); Block body = new Body(); Block title = new HeaderTitle(); Block avatar = new UserAvatar(); page.addBlock(header); page.addBlock(body); header.addBlock(title); header.addBlock(avatar); page.render();
페드로: 이것은 구조 패턴이에요. 개체들을 합성하는 좋은 방법이죠. 그래서 합성이라고 불리는 거죠.
이브: 저기요. 합성은 단순한 트리 구조네요.
페드로: 그렇죠.
이브: 모든 자료구조를 위한 패턴이 있나요?
페드로: 아니요. 단지 리스트와 트리만을 위한 패턴이 있죠.
이브: 사실, 트리는 리스트로 표현할 수 있어요.
페드로: 어떻게요?
이브: 리스트의 첫 요소는 노드이고요. 그 다음 요소들은 자식들이., 그리고 그들 각각은…
페드로: 이해했어요.
이브: 좀 더 설명하자면, 다음과 같이 트리가 있어요.
A / | \ B C D | | / \ E H J K / \ /|\ F G L M N
이브: 그리고 이 트리를 나타내는 리스트가 다음처럼 돼요.
(def tree '(A (B (E (F) (G))) (C (H)) (D (J) (K (L) (M) (N)))))
페드로: 괄호가 많네요!
이브: 괄호는 구조를 만드는 거죠, 알다시피.
페드로: 하지만 파악하기 어려워요.
이브: 기계한테는 쉽죠. 트리를 처리하는 tree-seq라는 멋진 함수가 있어요
(map first (tree-seq next rest tree)) => (A B E F G C H D J K L M N)
이브: 더 복잡한 순회가 필요하다면, clojure.walk를 사용하면 돼요.
페드로: 모르겠네요, 모든 것이 좀 더 어려워 보이네요.
이브: 아니요, 모든 트리를 자료구조 하나로 정의하고, 그것에 대해 동작하는 하나의 함수만을 사용하는 것이예요.
페드로: 이 함수가 하는 일이 뭐죠?
이브: 트리를 순회하면서 모든 노드에 함수를 적용하는 거죠, 우리의 경우에는 각 컴포넌트들을 랜더링하는 거구요.
페드로: 모르겠네요, 전 트리를 다루기에는 경험이 부족한가 봐요. 다음으로 가죠.