示例类图 最初,当开发人员看到访问者设计模式时,似乎可以使用多态轻松替换它,并且可以依赖于类的动态类型。但是,如果我们有一个庞大的类型层次结构呢?在这种情况下,每一次更改都必须更改一个接口,这将导致更改一大堆类,等等。 对于我们的类图和示例,让我们假设我们正在编写文本编辑器并且我们有文档。我们希望能够以至少两种数据格式保存每个文档,但可能会出现新的文档。下图显示了使用访问者设计模式的应用程序的类图:正如您在上图中所看到的,我们有两个看似断开的层次结构。左边的那个代表我们的文档 - 每个文档只是一个不同元素的列表。所有这些都是Element抽象类的子类,它有一个接受访问者的accept方法。在右侧,我们有访问者层次结构 - 我们的每个访问者将混合访问者特征,其中包含访问方法以及每个文档元素的覆盖。 访问者模式的工作方式是它将根据需要创建一个Visitor实例,然后将其传递给Document accept方法。这样,我们可以非常轻松地添加额外的功能(在我们的示例中为不同的格式),并且额外的功能不会涉及对模型的任何更改。 代码示例让我们一步一步地查看实现前一个示例的访问者设计模式的代码。首先,我们有我们的文档模型和可以构建它的所有元素:前面的代码没有什么特别之处,只是对不同文档元素的简单子类,以及Document类的组合及其包含的元素。这里重要的方法是接受。它需要访问者,并且由于给出了特征类型,我们可以传递不同的访问者实现。在所有情况下,它调用访问者的访问方法,并将当前实例作为参数传递。 现在,让我们来看看另一方面 - 访客特征及其实现。访问者特征看起来很简单:特征访问者{def访问(标题:标题)def访问(文本:文本)def访问(超链接:超链接)}在这种情况下,它具有不同具体元素类型的访问方法的重载。在前面的代码中,访问者和元素允许我们使用双重调度来确定将进行哪些调用。 现在,让我们看一下具体的Visitor实现。第一个是HtmlExporterVisitor:它根据它获得的Element类型提供不同的实现。没有条件语句,只是重载。 如果我们想以纯文本格式保存文档,我们可以使用PlainTextExporterVisitor:前面的示例显示了如何使用我们实现的访问者。我们程序的输出显示在以下屏幕截图中
abstract class Element(text: String) { def accept(visitor: Element => Unit): Unit = { visitor(this) } } case class Title(text: String) extends Element(text) case class Text(text: String) extends Element(text) case class Hyperlink(text: String, val url: String) extends Element(text) class Document(parts: List[Element]) { def accept(visitor: Element => Unit): Unit = { parts.foreach(p => p.accept(visitor)) } }
object VisitorExample { val line = System.getProperty("line.separator") def htmlExporterVisitor(builder: StringBuilder): Element => Unit = { case Title(text) => builder.append(s"<h1>${text}</h1>").append(line) case Text(text) => builder.append(s"<p>${text}</p>").append(line) case Hyperlink(text, url) => builder.append(s"""<a href=\"${url}\">${text}</a>""").append(line) } def plainTextExporterVisitor(builder: StringBuilder): Element => Unit = { case Title(text) => builder.append(text).append(line) case Text(text) => builder.append(text).append(line) case Hyperlink(text, url) => builder.append(s"${text} (${url})").append(line) } def main(args: Array[String]): Unit = { val document = new Document( List( Title("The Visitor Pattern Example"), Text("The visitor pattern helps us add extra functionality without changing the classes."), Hyperlink("Go check it online!", "https://www.google.com/"), Text("Thanks!") ) ) val html = new StringBuilder System.out.println(s"Export to html:") document.accept(htmlExporterVisitor(html)) System.out.println(html.toString()) val plain = new StringBuilder System.out.println(s"Export to plain:") document.accept(plainTextExporterVisitor(plain)) System.out.println(plain.toString()) } }