如何强制调用超类的方法,尽管它们已被覆盖(在Ocaml中)

时间:2011-01-28 14:57:19

标签: oop ocaml override late-binding

老实说,我对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调用)从子类执行overriden方法foo2。我希望它可以从超类中调用方法foo2,因为它是通过super调用的。

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

4 个答案:

答案 0 :(得分:4)

不,不是。这是“后期绑定”,是OO的基本思想,不是OCaml特有的。

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

答案 1 :(得分:1)

我对此并不是100%肯定,但我很确定你不能。在OCaml中,继承和子类型是两个不同的概念。类可以是没有继承的另一种类型的子类型。所有继承都是从继承类中引入方法。

多态性是通过结构化输入实现的,因此您对foo2的调用会调用bar中的方法,因为bar已实施foo2而因为bar继承了来自foo(就像C ++一样,由于查找虚函数表而调用bar#foo2)。

也就是说,OCaml确实为您提供了一种使用inherit...as...语法区分重写方法和继承方法的方法。在您的示例的bar中,o#foo1super#foo1是相同的方法(因为bar未实现foo1)而o#foo2和{ {1}}是不同的方法。尽管如此,我认为继承的类无论如何都不知道它已被继承并且区分它的方法和重写的方法。可能有一些语法,但我高度怀疑它是由于继承和多态是独立的概念。

答案 2 :(得分:1)

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

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

这是最简单,最干净,最清晰的方法:完成你想要的程序功能。

或者你应该向自己和我们解释:你为什么需要OOP?其原因未在问题文本中给出。为什么要使用foo1foo2方法而不是独立函数? (除了foo1foo2,您可以在程序中包含一些对象,类和方法(如果适用)。

想知道这个问题是否来自与其他lnaguages

的比较

如果你知道另一种OO语言,那么你想从OOP那里得到“那种行为”是很奇怪的:它不是Java或C ++中预期的行为,因为它们采用了与之相关的虚拟方法表的概念。每个对象在运行时,因此在程序中调用方法时,它会在运行时调度到实际与该对象关联的方法的实现。因此,简而言之:无论何时在程序中使用方法调用表达式,您都会遵循这一原则来查找方法的实现(“后期绑定”),正如gasche所指出的那样。虽然还是比照使用Niki Yoshiuchi指出的虚拟方法表实现的OCaml和语言之间的差异。

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

虽然您想要的可能不是许多流行的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可能是一种可能出现这种行为的语言,但是:

  • 仅适用于“类级别”编程(静态方法),而非对象级别;
  • 仅当您在函数调用中对某些奇怪的"late static binding"进行硬编码时:

#!/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 | head -20
model2
foo1
model2
foo1
model2
foo1
model2
endmodel
bar2
foo1
foo2
foo2
foo2
foo2
foo2
foo2
foo2
foo2
foo2
foo2
$ 

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

我不理解像static::这样疯狂的PHP绑定规范的用途,这些规范只对静态方法(即类级编程)有效。

默认情况下,类似的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 | head -10
bar2
foo1
foo2
foo2
foo2
foo2
foo2
foo2
foo2
foo2
$ 

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

Java的静态方法怎么样?(它们与C ++有什么不同,默认情况下给我们一个类似于虚方法的函数调用解决方案?)

答案 3 :(得分:1)

正如加油指出的那样,这是OO语言的预期和标准行为。

由于super#foo1未覆盖barfoo1来电与o#foo1完全相同。仅存在super构造,才能从foo2中的方法调用foo bar方法(否则无法引用该方法)。在从foo1调用的bar#foo2中,o实际上是bar,而不是foo,因此调用foo2方法会调用bar 1}} foo2方法。