2011-12-23 42 views

回答

41

是的,你應該從模式匹配開始,而不是訪客模式。看到這個interview with Martin Odersky(我的重點):

所以對於工作的工具實際上取決於哪個方向 要擴展。如果你想擴展新的數據,你可以用虛方法選擇經典的面向對象的方法。如果你想讓 保持數據的固定並以新操作進行擴展,那麼 更適合。實際上有一種設計模式 - 不是 與模式匹配相混淆 - 在面向對象的編程中被稱爲 訪問者模式,它可以代表我們用 模式匹配的一些事情,它以面向對象的方式,基於虛擬方法 派遣。 但實際使用中訪客模式非常龐大。你無法完成很多模式匹配很容易的事情。 你最終會遇到非常沉重的訪客。而且事實證明,現代虛擬機技術與模式匹配相比效率更低。 由於這兩個原因,我認爲模式 匹配有一定的作用。

編輯:我認爲這需要一些更好的解釋和例子。訪問者模式通常用於訪問樹或類似樹中的每個節點,例如抽象語法樹(AST)。以出色的Scalariform爲例。 Scalariform通過解析Scala然後遍歷AST來格式化scala代碼,寫出來。提供的方法之一是使用AST並按順序創建所有令牌的簡單列表。用於此的方法是:

private def immediateAstNodes(n: Any): List[AstNode] = n match { 
    case a: AstNode    ⇒ List(a) 
    case t: Token     ⇒ Nil 
    case Some(x)     ⇒ immediateAstNodes(x) 
    case xs @ (_ :: _)    ⇒ xs flatMap { immediateAstNodes(_) } 
    case Left(x)     ⇒ immediateAstNodes(x) 
    case Right(x)     ⇒ immediateAstNodes(x) 
    case (l, r)     ⇒ immediateAstNodes(l) ++ immediateAstNodes(r) 
    case (x, y, z)     ⇒ immediateAstNodes(x) ++ immediateAstNodes(y) ++ immediateAstNodes(z) 
    case true | false | Nil | None ⇒ Nil 
} 

def immediateChildren: List[AstNode] = productIterator.toList flatten immediateAstNodes 

這是很可能在Java中的訪問者模式來實現,但更簡潔的模式匹配Scala中所做的工作。在Scalastyle(Checkstyle for Scala)中,我們使用這種方法的一種修改形式,但有一個微妙的變化。我們需要遍歷樹,但每個檢查只關心某些節點。例如,對於EqualsHashCodeChecker,它只關心定義的equals和hashCode方法。我們用下面的方法:

protected[scalariform] def visit[T](ast: Any, visitfn: (Any) => List[T]): List[T] = ast match { 
    case a: AstNode    => visitfn(a.immediateChildren) 
    case t: Token     => List() 
    case Some(x)     => visitfn(x) 
    case xs @ (_ :: _)    => xs flatMap { visitfn(_) } 
    case Left(x)     => visitfn(x) 
    case Right(x)     => visitfn(x) 
    case (l, r)     => visitfn(l) ::: visitfn(r) 
    case (x, y, z)     => visitfn(x) ::: visitfn(y) ::: visitfn(z) 
    case true | false | Nil | None => List() 
} 

通知我們遞歸調用visitfn(),不visit()。這允許我們重複使用這種方法來遍歷樹而不需要複製代碼。在我們的EqualsHashCodeChecker,我們有:

private def localvisit(ast: Any): ListType = ast match { 
    case t: TmplDef  => List(TmplClazz(Some(t.name.getText), Some(t.name.startIndex), localvisit(t.templateBodyOption))) 
    case t: FunDefOrDcl => List(FunDefOrDclClazz(method(t), Some(t.nameToken.startIndex), localvisit(t.localDef))) 
    case t: Any   => visit(t, localvisit) 
} 

所以這裏的唯一樣板是在模式匹配的最後一行。在Java中,上面的代碼很可能作爲訪問者模式實現,但在Scala中,使用模式匹配是有意義的。還要注意的是,除了定義unapply()之外,上述代碼不需要修改正在遍歷的數據結構,如果您使用的是案例類,則會自動發生。

+0

尼斯。這是我在Scala中能夠找到訪問者模式的最佳示例代碼,其中訪問功能從特定「操作」代碼中分離出來。 – Core

+0

「訪問者模式通常用於訪問樹中的每個節點或類似地址」 - 恕我直言,這是一個常見的誤解。訪問者模式與訪問多個節點無關,儘管它的名稱不同。 GoF的原意是要有嚴格的ADT語義,當編譯器強制你處理所有聲明的子類型時。它指定了一組關閉的子類型,並且不允許添加非官方的子類型。 – beefeather

+0

當您擁有數百種節點類型並且您需要使用不同的代碼訪問每個節點時會發生什麼? –