# Interpreter Pattern 버티 프레이시(Bertie Prayc)가 우리 서버에서 중요한 데이터를 훔쳐서 비트토렌트(BitTorrent) 시스템을 통해 공유하고 있다. 버티의 가짜 계정을 만들어 그의 평판을 떨어뜨려야 한다. 페드로: 비트토렌트 시스템은 .torrent 파일에 기반하고 있어서 Bencode 인코더를 만들어야 해요. 이브: 네, 그렇다면 먼저 Bencode 포맷에 대해 알아야겠네요. 다음은 Bencode 인코딩 규칙이다. - 2개의 데이터 타입이 지원된다. - 정수 N은 ie로 인코딩된다. (예) 42 = i42e - 문자열 S는 :로 인코딩된다. (예) hello = 5:hello - 2개의 컨데이너가 지원된다. - 리스트는 le로 인코딩된다. (예) [1, "Bye"] = li1e3:Byee - 맵은 de로 인코딩된다. (예) {"R" 2, "D" 2} = d1:Ri2e1:Di2ee - 키는 단순히 문자열이고, 값에는 모든 Bencode 요소가 허용된다. 페드로: 쉬워 보이네요 이브: 그럴 수도 있지만, 값들은 중첩될 수 있다는 점을 고려해야 해요. 예를 들면 리스트 안의 리스트 같은. 페드로: 물론이죠. bencode 인코딩에는 해석자 패턴을 사용할 수 있을 것 같네요. 이브: 한번 해 보시죠. 페드로: 모든 bencode 요소들을 위한 인터페이스부터 시작해 보죠. interface BencodeElement { String interpret(); } 페드로: 그리고 나서 각각의 데이터 타입과 데이터 컨데이너에서 위의 인터페이스를 구현합니다. class IntegerElement implements BencodeElement { private int value; public IntegerElement(int value) { this.value = value; } @Override public String interpret() { return "i" + value + "e"; } } class StringElement implements BencodeElement { private String value; StringElement(String value) { this.value = value; } @Override public String interpret() { return value.length() + ":" + value; } } class ListElement implements BencodeElement { private List list; ListElement(List list) { this.list = list; } @Override public String interpret() { String content = ""; for (BencodeElement e : list) { content += e.interpret(); } return "l" + content + "e"; } } class DictionaryElement implements BencodeElement { private Map map; DictionaryElement(Map map) { this.map = map; } @Override public String interpret() { String content = ""; for (Map.Entry kv : map.entrySet()) { content += kv.getKey().interpret() + kv.getValue().interpret(); } return "d" + content + "e"; } } 페드로: 마지막으로 bencode 인코딩 문자열을 만들 수 있어요. // discredit user Map mainStructure = new HashMap(); // our victim mainStructure.put(new StringElement("user"), new StringElement("Bertie")); // just downloads files mainStructure.put(new StringElement("number_of_downloaded_torrents"), new IntegerElement(623)); // and nothing uploads mainStructure.put(new StringElement("number_of_uploaded_torrents"), new IntegerElement(0)); // and nothing donates mainStructure.put(new StringElement("donation_in_dollars"), new IntegerElement(0)); // prefer dirty categories mainStructure.put(new StringElement("preffered_categories"), new ListElement(Arrays.asList( new StringElement("porn"), new StringElement("murder"), new StringElement("scala"), new StringElement("pokemons") ))); BencodeElement top = new DictionaryElement(mainStructure); // let's totally discredit him String bencodedString = top.interpret(); BitTorrent.send(bencodedString); 이브: 재미있네요, 그런데 코드량이 너무 많네요! 페드로: 기능이 많다 보니 읽기 어려워졌어요. 이브: 코드가 곧 데이터(Code is Data)라는 말을 들어 보신 적 있을 거예요. 그래서 클로저에서는 훨씬 수월해지죠. ;; multimethod to handle bencode structure (defmulti interpret class) ;; implementation of bencode handler for each type (defmethod interpret java.lang.Long [n] (str "i" n "e")) (defmethod interpret java.lang.String [s] (str (count s) ":" s)) (defmethod interpret clojure.lang.PersistentVector [v] (str "l" (apply str (map interpret v)) "e")) (defmethod interpret clojure.lang.PersistentArrayMap [m] (str "d" (apply str (map (fn [[k v]] (str (interpret k) (interpret v))) m)) "e")) ;; usage (interpret {"user" "Bertie" "number_of_downloaded_torrents" 623 "number_of_uploaded_torrent" 0 "donation_in_dollars" 0 "preffered_categories" ["porn" "murder" "scala" "pokemons"]}) 이브: 데이터를 정의하는 것이 얼마나 쉬운지 보이세요? 페드로: 그러네요.interpret은 bencode 자료형 당 단순히 하나의 함수네요. 별도의 클래스가 아니라. 이브: 맞아요, 해석자 패턴은 단순히 트리를 처리하는 함수들일 뿐인거죠.