说明:在访问者模式中,可访问的类(实体)有一个方法.accept(),其作用是调用访问者的.visit()方法.我可以看到java示例的逻辑:访问者为它支持的每个可访问类型定义了一个不同的.visit(n)方法,并且必须使用.accept()技巧在运行时选择它们.但是像python或PHP这样的语言有动态类型,没有方法重载.如果我是访问者,我可以在不知道实体的类型甚至方法的完整签名的情况下调用实体方法(例如.serialize()). (这是“双重调度”问题,对吧?)
我知道一个接受方法可以将受保护的数据传递给访问者,但有什么意义呢?如果数据公开给访问者类,它实际上是类接口的一部分,因为它的详细信息在类之外很重要.无论如何,公开私人数据从来没有让我感到自己是访客模式的重点.
因此,似乎在python,ruby或PHP中我可以在访问对象中没有接受方法(并且没有反射)的情况下实现类似访问者的类,对吧?如果我可以使用一系列异构对象并在没有“访问”类的任何合作的情况下调用他们的公共方法,那么这仍然应该被称为“访问者模式”吗?是否存在我缺失的模式的本质,或者它是否只是归结为“编写一个从外部操作对象来执行操作的新类”?
PS.我已经看过很多关于SO和其他地方的讨论,但找不到任何解决这个问题的东西.指针欢迎.
Visitor的另一个好处是它提供了一个干净的分离代码,它可以从枚举实体的代码中运行每个实体.这在至少一个大型项目中为我节省了一些严重的代码重复.
顺便说一句,我在没有方法重载的语言中使用了Visitor.您只需使用VisitN(TypeN n)替换Visit(TypeN n).
跟进评论.
这是访问者伪造的代码,如果没有访问对象的合作(至少没有切换块),我不知道如何这样做:
abstract class ScriptCommand { void Accept(Visitor v); } abstract class MoveFileCommand { string TargetFile; string DestinationLocation; void Accept(Visitor v) { v.VisitMoveFileCmd(this); // this line is important because it eliminates the switch on object type } } abstract class DeleteFileCommand { string TargetFile; void Accept(Visitor v) { v.VisitDeleteFileCmd(this); // this line is important because it eliminates the switch on object type } } // etc,many more commands abstract class CommandVisitor { void VisitMoveFileCmd(MoveFileCommand cmd); void VisitDeleteFileCmd(DeleteFileCommand cmd); // etc } // concrete implementation class PersistCommandVisitor() inherits CommandVisitor { void VisitMoveFileCmd(MoveFileCommand cmd) { // save the MoveFileCommand instance to a file stream or xml doc // this code is type-specific because each cmd subtype has vastly // different properties } void VisitDeleteFileCmd(DeleteFileCommand cmd) { // save the DeleteFileCommand instance to a file stream or xml doc // this code is type-specific because each cmd subtype has vastly // different properties } }
访问者基础结构允许处理各种命令子类型,没有选择案例,swithc,if else.
对于处理枚举的访问者,我认为你是这样限制自己的.这并不是说合作类(抽象的VisitorEnumerator)不能参与其中.
例如,请注意此访问者不知道枚举的顺序:
class FindTextCommandVisitor() inherits CommandVisitor { string TextToFind; boolean TextFound = false; void VisitMoveFileCmd(MoveFileCommand cmd) { if (cmd.TargetFile.Contains(TextToFind) Or cmd.DestinationLocation.Contains(TextToFind)) TextFound = true; } void VisitDeleteFileCmd(DeleteFileCommand cmd) { // search DeleteFileCommand's properties } }
这允许它像这样重复使用:
ScriptCommand FindTextFromTop(string txt) { FindTextCommandVisitor v = new FindTextCommandVisitor(); v.TextToFind = txt; for (int cmdNdx = 0; cmdNdx < CommandList.Length; cmdNdx++) { CommandList[cmdNdx].Accept(v); if (v.TextFound) return CommandList[cmdNdx]; // return the first item matching } }
并列举与同一访客相反的方式:
ScriptCommand FindTextFromBottom(string txt) { FindTextCommandVisitor v = new FindTextCommandVisitor(); v.TextToFind = txt; for (int cmdNdx = CommandList.Length-1; cmdNdx >= 0; cmdNdx--) { CommandList[cmdNdx].Accept(v); if (v.TextFound) return CommandList[cmdNdx]; // return the first item matching } }
在实际代码中,我将为枚举器创建一个基类,然后将其子类化以处理不同的枚举场景,同时传递具体的Visitor子类以完全解耦它们.希望你能看到保持枚举分离的力量.