Php 访问者模式对动态类型语言有用吗?

Php 访问者模式对动态类型语言有用吗?,php,python,ruby,design-patterns,visitor,Php,Python,Ruby,Design Patterns,Visitor,访问者模式允许在不扩展对象类的情况下编写对象上的操作。当然但为什么不编写一个全局函数或静态类,从外部操纵我的对象集合呢?基本上,在java这样的语言中,出于技术原因需要一个accept()方法;但是在一种不用accept()方法就可以实现相同设计的语言中,访问者模式会变得微不足道吗 说明:在Visitor模式中,可访问类(实体)有一个方法.accept(),其任务是在自身上调用访问者的.visit()方法。我可以看到java示例的逻辑:访问者为其支持的每个可访问类型定义了一个不同的.visit(

访问者模式允许在不扩展对象类的情况下编写对象上的操作。当然但为什么不编写一个全局函数或静态类,从外部操纵我的对象集合呢?基本上,在java这样的语言中,出于技术原因需要一个
accept()
方法;但是在一种不用
accept()
方法就可以实现相同设计的语言中,访问者模式会变得微不足道吗

说明:在Visitor模式中,可访问类(实体)有一个方法
.accept()
,其任务是在自身上调用访问者的
.visit()
方法。我可以看到java示例的逻辑:访问者为其支持的每个可访问类型定义了一个不同的
.visit(n)
方法
n
,并且在运行时必须使用
.accept()
技巧从中进行选择。但是像python或php这样的语言具有动态类型,并且没有方法重载。如果我是访问者,我可以调用实体方法(例如,
.serialize()
),而不知道实体的类型,甚至不知道方法的完整签名。(这就是“双重派遣”问题,对吗?)

我知道accept方法可以将受保护的数据传递给访问者,但这有什么意义呢?如果数据向访问者类公开,那么它实际上是类接口的一部分,因为它的细节在类之外很重要。无论如何,公开私人数据从来没有让我觉得是访问者模式的重点

因此,在python、ruby或php中,我似乎可以在访问对象中实现一个类似访问者的类,而不需要accept方法(也不需要反射),对吗?如果我可以处理一系列异构对象并调用它们的公共方法,而不需要“visited”类的任何合作,那么这仍然值得称为“Visitor模式”吗?模式的本质是否有我所缺少的东西,或者它只是归结为“编写一个从外部操纵对象以执行操作的新类”


另外,我已经看过很多关于某某和其他地方的讨论,但找不到任何能解决这个问题的东西。欢迎使用指针。

也许,这取决于语言

visitor模式解决了不具有特性的语言中的双重和多重层次结构问题。以Ruby、Lisp和Python为例。它们都是动态类型语言,但在标准中只有CLOS Lisp实现了多分派。这也称为多方法,Python和Ruby显然可以通过使用扩展来实现它

我喜欢这种奇怪的评论,它说:

Lisp的对象系统[CLOS]及其多分派并不能取代访问者模式, 但是仅仅提供了一个更简洁的实现,其中模式 消失了


在其他语言中,即使是静态类型的语言,也必须解决缺少多方法的问题。访问者模式就是这样一种方式

我认为您正在交替使用访问者模式和双重分派。当你说

如果我可以处理一系列异构对象并调用它们的公共方法,而不需要“visited”类的任何合作,那么这仍然值得称为“Visitor模式”吗

编写一个新类,从外部操纵对象以执行操作“

您正在定义什么是双重分派。当然,访问者模式是通过双重分派实现的。但是,模式本身还有更多的东西

  • 每个访问者都是一组元素(实体)上的算法,新访问者可以插入,而无需更改现有代码。打开/关闭原则
  • 频繁添加新元素时,最好避免访问者模式

这个答案是在不了解PHP等的情况下做出的,但是访问者通常需要对实体调用不止一个方法(您提到的“序列化”)方法,该访问者能够为每个实体子类型运行截然不同的代码。我看不出这与动态类型语言有什么不同(尽管我希望得到一些反馈)

Visitor的另一个好处是,它将在每个实体上运行的代码与枚举实体的代码完全分离。这至少在一个大型项目中为我节省了一些严重的代码重复

顺便说一句,我在没有方法重载的语言中使用了Visitor,您只需将Visit(TypeN)替换为VisitN(TypeN)


跟进评论

这是一个访问者psuedo代码,如果没有被访问对象的配合(至少没有开关块),我不知道如何实现它:

visitor基础结构允许处理大量的命令子类型,无需选择大小写swithc(如果有)

关于处理枚举的访问者,我认为您这样限制了自己。这并不是说不能涉及协作类(抽象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
   }
}

在实际代码中,我会为枚举器创建一个基类,然后将其子类化以处理不同的枚举场景,同时传入具体的Visitor子类以将它们完全解耦。希望您能看到保持枚举分离的威力。

Visitor特别有用的地方是VisitoR需要切换访问者的类型,并且出于任何原因,您不想将这些知识编码到Visistee(认为插件架构)。请考虑下面的Python代码:

访客风格 (注意,我们可以使用基类/mixin压缩Visite逻辑)

与之相比:

非访客风格 在
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
   }
}
class Banana(object):
      def visit(self, visitor):
          visitor.process_banana(self) 

class Apple(object):
      def visit(self, visitor):
          visitor.process_apple(self) 

class VisitorExample(object):
      def process_banana(self, banana):
          print "Mashing banana: ", banana

      def process_banana(self, apple):
          print "Crunching apple: ", apple
class NonVisitorVisitor(object):
      def process(self, fruit):
          verb = {Banana: "Mashing banana: ", 
                  Apple: "Crunching apple: "}[type(fruit)]
          print verb, fruit
def ASTNode__stringify(self):
    text = str(self)
    for child in self.children:
            text += ", { " + child.stringify() + " }"
    return text

def ASTConst__stringify(self):
    text = str(self)
    for child in self.children:
            text += ", [ " + child.stringify() + " ]"
    return text

def ASTIf__stringify(self):
    text = str(self)
    text += "__cond( " + self.op1.stringify() + ")"
    text += "__then { " + self.op2.stringify() + "}"
    text += "__else {" + self.op3.stringify() + "}"
    return text