這章的焦點焦點放在 visitor pattern 上面,因為對於這個 design pattern 不太熟,所以先來複習一下。
需求和動機
首先,在 visitor 架構中有一組多形的資料結構,需要支援一些操作。這應該很常見,像是線上商店可能會把書籍和音樂條列在一起,顯示的圖示不同,點選以後分別可以試閱或者試聽。因為要條列在一起,所以 Book & Music 要繼承同一個父類別,並且提供不同的 display() & preview() 實作。這樣使得資料和功能都寫在同一個 class,但這兩者應該分開,因為資料很少變動,可是功能經常修改或增減。Visitor pattern 可以把功能從資料抽出來,把每個功能變成一個 Visitor class,而資料提供一個轉接的 virtual accept(Visitor&),只要傳入不同的 Visitor 就可以執行不同的功能。以線上商店的例子來說,Book::accept(DisplayVisitor) 會顯示書籍的圖示,而 Music::accept(PreviewVisitor) 會播放試聽的音樂。如果要修改書籍的圖示,單獨修改 DisplayVisitor 不會影響原有的資料,也不會影響 PreviewVisitor 的功能。
Cylic Dependency
上圖中,Visitee->AbstractVisitor->A 三者間有循環依賴的關係,雖然只是 depend by name,通常使用 forward declaration 就可以。問題是,如果增加一個 C 繼承 Visitee,並且針對 C 提供對應的 virtual AbstractVisitor::visit(C&),那麼所有繼承 Visitee 的 class 像是 A & B 都要重新編譯,因為AbstractVisitor 增加了新的 virtual function。
不管重新編譯的範圍有多大,可能都是很嚴重的問題,因為繼承的基本精神是「相同界面,不同實作」。上圖的 A & B 可能在兩個不同 library 裡面,根本看不到 source code,如果需要重新編譯,就是很大的 API change。
Acyclic Visitor
上圖把原本的 visit() 獨立出來,讓 A & B 各自使用各自的 Visitor,避免互相依賴。在實作裡, A::accept(AbstractVisitor& v) 因為參數提供的型別訊息不足,所以需要用 dynamic_cast<Visitor<A>&>(v) 以後才能呼叫 visit(*this)。當然,也是因為用更抽象的參數,才能避免 cyclic dependency。
比較兩張架構圖,可以發現 acyclic visitor 把型別檢查從編譯延遲到執行,以便得到 A & B 獨立的好處。假設 ConcreteVisitor 在新增 C 的時候沒有實作 Visit(C&),那麼 cyclic visitor 會 compile error,但 acyclic visitor 要到執行才會出錯。另外一個問題是 dynamic_cast 的效率,比起原來直接呼叫 virtual function 可能要多數十倍。
No comments:
Post a Comment