4

老实说,我对 OCaml 的对象系统了解不多。以下片段说明了我的问题:

class foo =
  object (o)

    method foo1 y =
      print_endline "foo1";
      o#foo2 (y - 1)

    method foo2 y =
      print_endline "foo2";
      if y > 0 then o#foo2 y else print_endline "end"
  end

class bar =
  object (o : 'self_type)
    inherit foo as super

    method foo2 y =
    print_endline "bar2";
    if y > 0 then super#foo1 y else print_endline "endbar"

  end

let _ = (new bar)#foo2 3

执行时,代码段会产生以下输出:

bar2
foo1
bar2
foo1
bar2
endbar

,显示超类方法 foo1(通过 super#foo1 调用)从子类执行覆盖的方法 foo2。相反,我希望它从超类调用 foo2 方法,因为它是通过 super 调用的。

是否有可能实现这种行为,即有一个超类方法只调用其他超类方法,即使它们在子类中被覆盖?

4

4 回答 4

4

不,这不对。这是“后期绑定”,是 OO 的基本思想,并非特定于 OCaml。

我不知道您要做什么,但是面向对象的编程可能不是正确的工具,或者至少不是那样使用。如果你想学习 OCaml,你可能应该专注于非 OO 部分,这对于开始来说已经足够有趣了。

于 2011-01-28T16:11:43.527 回答
1

正如gasche 所指出的,这是面向对象语言的预期和标准行为。

super#foo1调用,因为bar不覆盖foo1,完全等同于o#foo1。该super构造仅存在能够调用from 方法中foo2的方法(否则无法引用该方法)。In as called from ,实际上是 a ,而不是 a ,因此调用该方法会调用该方法。foobarfoo1bar#foo2obarfoofoo2bar foo2

于 2011-01-28T16:43:40.000 回答
1

我想说,如果你想硬编码这种行为,你最好不要使用面向对象的编程。只需将其实现为调用其他函数的函数。

(我理解的“那种行为”:如果foo2从被称为 as 的代码内部调用super#foo1,那么foo2应该调用超类的实现,而不是来自子类的更“具体”的实现)

这是最简单、最干净、最清晰的方法:按照您的意愿编写函数。

或者您应该向自己和我们解释:为什么需要 OOP?问题文本中没有给出原因。为什么使用 makefoo1foo2方法而不是独立的函数?(除了foo1and foo2,你的程序中可能有一些对象、类和方法,如果合适的话)。

想知道这个问题是否来自与其他语言的比较

如果您知道另一种 OO 语言,那么您希望 OOP 中的“那种行为”很奇怪:这不是 Java 或 C++ 中所期望的行为,因为它们采用与每个对象相关联的虚拟方法表的概念运行时,因此当在程序中调用方法时,它会在运行时分派到与对象实际关联的该方法的实现。因此,简而言之:每当您在程序中使用方法调用表达式时,您就承诺遵循这一原则,即找到方法的实现(“后期绑定”),正如 gasche 所指出的那样。虽然仍然比较。OCaml 与使用 Niki Yoshiuchi 指出的虚拟方法表实现的语言之间的差异。

将所有可用和想要的行为的讨论形式化

尽管您想要的可能不是许多流行的 OO 语言中的预期和可用行为,但如果可以访问 OOP 实现内部,它是可以想象的并且可以在某些特定的 OOP 系统中实现。

说,如果在某些实现中,super是一个保存超类的方法表的结构(在解决当前对象的方法调用时回退到),并且方法是必须接收对象(方法表)作为第一个的函数arg,然后执行你想要的,一个人会写super.foo1(super, y).

(我实际上想知道是否有 OOP 实现的内部结构暴露给程序员并允许进行这样的调用。)

而通常的 OOP 行为将在此系统中表示为(当前对象的方法表在this.foo1(this, y)哪里。)this

您的 OCaml 调用super#foo1 y或 Java会将super.foo1(y);这个“我的”系统转换为super.foo1(this, y). (尽管仍然可以参见 Niki Yoshiuchi 指出的 OCaml 与使用虚拟方法表实现的 Java 等语言之间的差异。)

您会看到这 3 种情况之间的差异。

附录。寻找可以以这种方式工作的语言

嗯,PHP 可能是一种可以实现这种行为的语言,但是:

  • 仅在“类级”编程(静态方法)上,而不是对象级;
  • 仅当您在函数调用中硬编码一些奇怪的“后期静态绑定”时:

#!/usr/bin/php
<?php
class Foo {
  public static function foo1($y) {
    echo "foo1\n";
    static::foo2($y-1);
  }
   
  public static function foo2($y) {
    echo "foo2\n";
    if ($y > 0)
      static::foo2($y);
    else
      echo "end\n";
  }
  }
   
class Bar extends Foo {
  public static function foo2($y) {
    echo "bar2\n";
    if ($y > 0)
      Foo::foo1($y);
    else
      echo "endbar\n";
  }
}

class Model extends Foo {
  public static function foo2($y) {
    echo "model2\n";
    if ($y > 0)
      static::foo1($y);
    else
      echo "endmodel\n";
  }
}

Model::foo2(3);

Bar::foo2(3);
?>

从某种意义上说,模型的行为类似于具有虚拟方法的标准 OO 对象,以及您想要的 Bar :

$ ./test-force-super-binding.php | 头 -20
模型2
foo1
模型2
foo1
模型2
foo1
模型2
终端模型
酒吧2
foo1
foo2
foo2
foo2
foo2
foo2
foo2
foo2
foo2
foo2
foo2
$

(顺便说一句,使用parent::而不是Foo::不会让我们达到您想要的行为。)

我不明白疯狂的 PHP 绑定规范的目的,例如static::,它们仅对静态方法(即类级编程)有一些影响。

默认情况下,类似的 C++ 示例不提供 OO 对象级行为:


#include<iostream>

class Foo {
protected:
  static void foo1(int y) {
    std::cout << "foo1" << std::endl;
    foo2(y-1);
  }

public:
  static void foo2(int y) {
    std::cout << "foo2" << std::endl;
    if (y > 0)
      foo2(y);
    else
      std::cout << "end" << std::endl;
  }

};
  
class Bar: public Foo {
public:
  static void foo2(int y) {
    std::cout << "bar2" << std::endl;
    if (y > 0)
      foo1(y);
    else
      std::cout << "endbar" << std::endl;
  }
};

int main() {
  Bar::foo2(3);
  return 0;
}

-- 它给出了你想要的行为:

$ ./a.out | 头-10
酒吧2
foo1
foo2
foo2
foo2
foo2
foo2
foo2
foo2
foo2
$

即使在代码中的函数调用中没有任何特殊限定符 for Bar::foo2(),所以对我们来说并不有趣。

Java 的静态方法呢?..(它们是否与 C++ 不同,并且默认为我们提供了类似虚拟方法的函数调用解析?)

于 2011-01-28T16:20:34.693 回答
1

我不是 100% 确定这一点,但我很确定你不能。在 OCaml 中,继承和子类型是两个不同的概念。一个类可以是另一种类型的子类型,无需继承。所有继承都是从继承的类中提取方法。

Polymorphism is achieved via structural typing, so your call to foo2 calls the method in bar because bar has implemented foo2 and NOT because bar inherits from foo (as apposed to say, C++, where bar#foo2 is called due to a virtual function table look up).

That said, OCaml does provide you with a way to distinguish between overridden methods and inherited methods using the inherit...as... syntax. In bar from your example, o#foo1 and super#foo1 are the same method (since bar doesn't implement foo1) whereas o#foo2 and super#foo2 are different methods. Despite this, I don't think there is anyway for the inherited class to know that it has been inherited and distinguish between it's methods and the overridden methods. There might be some syntax for that but I highly doubt it due to the fact that inheritance and polymorphism are independent concepts.

于 2011-01-28T16:11:39.070 回答