感觉很难理解,因为几乎所有的教程/教材,样例代码或者实际代码里面,提到观察者模式的时候,都是清一色的用返回 void 的方法来 visit 和 accept ,然后依赖副作用和全局变量来返回结果。
感觉这样的代码很难懂而且很绕,也可能因为我比起命令式或 OOP 代码更容易理解纯函数式代码吧。
就,比如说我试图理解 visitor pattern 的话,我会把它当成一个「在命令式语言里通过动态分派实现模式匹配」的技巧。自然而然的就会想出这样的代码,比如说在 Java 里的话:
public ISomeVisitor<T> {
public T visit(DerivedDataTypeA data);
public T visit(DerivedDataTypeB data);
...
}
public IDataType {
public T accept(ISomeVisitor visitor);
}
然后在 visitor 的具体实现里面,就只需要去重写然后使用 DerivedDataType 里面的访问方法去处理它,然后返回一个转换后的?T 类型。同时,由于每个 visit 分支都返回相同的类型,它们可以被组合起来,看起来就跟比如 Scala 或者 ML 语言里的模式匹配是同样的方式。
嗯,这个是理想情况。
但我记得在大学里学习 OOP 然后第一次按照这个路子写 visitor 后,就被其他人纠正说我实现的方法不对,因为我在 visit 函数里实现了具体逻辑。嗯,我一直没能搞懂为什么我实现错了,其实现在我也没搞懂。
但是我其实对返回 void 的方法和没有参数化泛型的类感觉更难理解,大概有这么几个原因吧:
public Result returnedResult;
在第 19 行,然后我的几十个分支里面都去 mutate 这个Result
来返回一次访问的结果,就像刚才说的,没有任何机制可以让我不去随意修改这个returnedResult
来破坏 visitor pattern 的模式。但同时,一个字段可以在第 19 行,第 199 行和第 1099 行被修改,这也破坏了「就近原则」吧?而且这里也可以看到一个潜在的 NPE 热点。所以觉得这样的代码既难以理解又难以维护。
当然也许有人会说顺序很重要,但是一般来说 visitor 在业务代码例如 web 应用里面都是用在树状数据结构上,这种使用场景应该没差?
所以我感觉困惑的大概就是,我这个 visitor 的实现思路错在了哪里?为什么几乎清一色的所有 visitor 的代码实现都是返回 void 方法并且通过副作用修改全局变量来储存返回计算结果的?这样做是为了什么呢?
然后这个草稿写完之后又读了一本叫 A little Java, A few Patterns 的书,就感觉更困惑了。因为这本书里的 visitor 不但也是不依赖副作用而是返回值的,它的 visit 函数甚至还可以接受多个参数,看起来更不符合 visitor 的一般定义。
所以我该怎么理解这个 visitor pattern 呢?
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.