PHP:如何从父类调用子类的函数

76

如何从父类中调用子类的函数?考虑以下代码:

class whale
{
  function __construct()
  {
    // some code here
  }

  function myfunc()
  {
  // how do i call the "test" function of fish class here??
  }
}

class fish extends whale
{
  function __construct()
  {
    parent::construct();
  }

  function test()
  {
    echo "So you managed to call me !!";
  }

}

我知道我使用了错误的类名,但这只是一个例子,不是我会在任何地方实现的东西。 - Sarfraz
12
从逻辑的角度来看,“鱼”应该是父类,“鲸鱼”是子类 ;) - Strae
22
也许鱼应该是子类,因为它在被鲸吃掉后驻留在鲸体内。 - Chris Harrison
如果你好奇,鲸鱼是哺乳动物,不是鱼类。 - 8ctopus
15个回答

134

那就是抽象类的用途。抽象类基本上告诉我们:谁继承我,必须拥有这个函数(或这些函数)。

abstract class whale
{

  function __construct()
  {
    // some code here
  }

  function myfunc()
  {
    $this->test();
  }

  abstract function test();
}


class fish extends whale
{
  function __construct()
  {
    parent::__construct();
  }

  function test()
  {
    echo "So you managed to call me !!";
  }

}


$fish = new fish();
$fish->test();
$fish->myfunc();

28
虽然这句话本身是正确的,但我认为他想执行 $whale = new Whale; $fish = new Fish; ,然后 $whale->myfunc(); 应该调用 $fish->test(); 而不知道 $fish 存在。 - Gordon
1
+1,抽象方法通常是您在父类引用子类中的方法时所遇到的问题的答案。如果Gordon正确并且您真的想要做一些不同/具体的事情,那么您应该澄清。否则,这应该被接受。 - philfreo
我也搜索了这个问题,但是你的答案并没有解决我的问题:whale = new whale(); $whale->test(); :( 所以我正在考虑一件事情 - 当调用鲸控制器时,只需重定向到鱼控制器并调用。但不知何故,重定向感觉有点滞后。 - Dariux
@FlorianH 我有一个问题,我们无法创建鲸类的对象,因为抽象类对象的创建是不可能的。但在我的情况下,父类中有一些其他函数,我该如何轻松地管理,请建议是否有任何方法。 - Sachin Sarola
当方法是静态的时候,是否可以以静态方式实现? - Irvan Hilmi

36

好的,这个答案非常晚,但是为什么没有人想到这个呢?

Class A{
    function call_child_method(){
        if(method_exists($this, 'child_method')){
            $this->child_method();
        }
    }
}

而该方法在扩展类中被定义:

Class B extends A{
    function child_method(){
        echo 'I am the child method!';
    }
}

那么使用以下代码:

$test = new B();
$test->call_child_method();

输出结果为:

I am a child method!

我使用这个来调用钩子方法,这些方法可以由一个子类定义,但不一定必须如此。


2
如果所有子类都有相同的方法,您还可以考虑实现一个接口到子类。这样,您就能够在父类中排除method_exists() - Ben Fransen
@Christian Engel,也许你错了,提问者想要做的是创建类A的对象,并使用该对象尝试调用类B的方法。也许我的理解有误。 - Sachin Sarola
7
事实上,你正在调用类B的一个方法来调用该类的另一个方法。 :| - Rain
是的,绝对正确 @rain!这个模式有趣的原因在于,你可以忽略子类是否实现了给定的方法。"守卫"或钩子方法始终存在,检测子类是否实现了“真正”的方法并调用它。 - Christian Engel
1
除了这违反单一职责原则之外,还不能保证没有人会直接调用 $test->child_method()。我认为,如果唯一重要的事情是某个类是否有某个方法,那么最好使用接口契约。然后检查该类是否实现了预期的接口。 - Rain

18
从技术上讲,您不能从鲸鱼实例(父级)调用鱼实例(子级),但由于您正在处理继承,因此无论如何都可以在您的鱼实例中使用myFunc(),因此您可以直接调用$yourFishInstance->myFunc()。
如果您正在引用template method pattern,那么只需将$this->test()编写为方法体即可。从fish instance调用myFunc()将把调用委托给鱼实例中的test()。但同样,不允许从鲸鱼实例调用鱼实例。
顺便说一下,鲸鱼是哺乳动物而不是鱼;)

2
这只是一个例子,伙计,我不会在任何地方实现这些名称 :) - Sarfraz

14

嗯,好吧,这个问题有很多地方出了问题,我不知道从哪里开始。

首先,鱼不是鲸鱼,鲸鱼是哺乳动物。

其次,如果你想从父类中调用子类的函数,而该函数在父类中不存在,那么你的抽象化严重有问题,应该重新考虑。

第三,在PHP中,你可以这样做:

function myfunc() {
  $this->test();
}

在使用whale实例时,会导致错误。但是在使用fish实例时,应该能够正常工作。


11
+1 赞同-首先,鱼不是鲸鱼,鲸鱼也不是鱼。鲸鱼属于哺乳动物。 - mamdouh alramadan
@cletus 的回答非常好,这意味着父类不应该对其子类有如此亲密的了解,对吗?+1 预先致谢。 - user19481364
1
一个父类不应该依赖于子类,但是子类可能会依赖于父类。 - user19481364

14

自PHP 5.3起,您可以使用static关键字从调用的类中调用方法,例如:

<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        static::who(); // Here comes Late Static Bindings
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}

B::test();
?>

以上示例将输出: B

来源:PHP.net / Late Static Bindings


2
太好了,我感激不尽。我在静态类中遇到了困难,因为我没有实例可以调用,这是我唯一解决它的方法。 - azerafati
1
@TomSawyer,你能否提供任何证明或基准测试结果吗? - merkdev

3
我知道这可能对你来说有些晚了,但我也不得不解决这个问题。为了帮助其他人理解为什么这有时是必需的,这是我的例子:
我正在为一个应用程序构建一个MVC框架,我有一个基本控制器类,每个单独的控制器类都是通过它来扩展的。每个控制器将具有不同的方法,具体取决于控制器需要做什么。例如,mysite.com/event 将加载事件控制器。mysite.com/event/create 将加载事件控制器并调用“create”方法。为了标准化调用创建函数,我们需要基本控制器类访问子类的方法,这对每个控制器都是不同的。因此,在代码上,我们有父类:
class controller {

    protected $aRequestBits;

    public function __construct($urlSegments) {
        array_shift($urlSegments);
        $this->urlSegments = $urlSegments;      
    }

    public function RunAction($child) {
        $FunctionToRun = $this->urlSegments[0];
        if(method_exists($child,$FunctionToRun)) {
            $child->$FunctionToRun();
        }
    }   
}

然后是子类:

class wordcontroller extends controller {

    public function add() {
        echo "Inside Add";
    }

    public function edit() {
        echo "Inside Edit";
    }

    public function delete() {
        echo "Inside Delete";
    }
}

因此,在我的情况下,解决方案是将子实例本身作为参数传递回父类。

你不需要传递$child引用。使用if(method_exists($this,$FunctionToRun))就足够了。请记住,wordcontroller继承了controller中的所有函数,因此wordControlObj->runAction()将检查wordcontroller和controller类中是否存在函数。如果wordController覆盖了controller中的任何方法,则将调用wordcontroller版本。 如果您恰好有一个调试器,跟踪运行流程的简单方法是通过使用以下内容在wordcontroller中覆盖该方法: function RunAction(){parent :: RunAction()} - tanovellino

3
唯一能够做到这一点的方法是通过反射。但是,反射是昂贵的,应该只在必要时使用。
真正的问题在于父类不应该依赖于子类方法的存在。这是面向对象设计的指导原则,并表明您的设计存在严重缺陷。
如果您的父类依赖于特定的子类,则它不能被任何其他可能扩展它的子类使用。父子关系从抽象到具体,而不是相反。最好将所需的函数放在父类中,如果需要,可以在子类中覆盖它。例如:
class whale
{
  function myfunc()
  {
      echo "I am a ".get_class($this);
  }
}

class fish extends whale
{
  function myfunc()
  {
     echo "I am always a fish.";
  }
}

我怀疑用反射可以完成这个任务。鲸鱼必须在某个地方硬编码鱼的信息。 - Gordon

3

我会选择抽象类...
但是在PHP中,您不必使用它们就可以使其正常工作。甚至调用父类的构造函数也是一种“普通”的方法调用,此时对象已经完全“运行”,也就是说,$this“知道”所有成员,无论是继承还是非继承的。

class Foo
{
  public function __construct() {
    echo "Foo::__construct()\n";
    $this->init();
  }
}

class Bar extends Foo
{
  public function __construct() {
    echo "Bar::__construct()\n";
    parent::__construct();
  }

  public function init() {
    echo "Bar::init()\n";
  }
}

$b = new Bar;

打印
Bar::__construct()
Foo::__construct()
Bar::init()

即使 class Foo 不知道 function init() 的任何信息,它也可以调用该方法,因为查找是基于 $this 引用的。 这是技术方面的。但您真的应该通过使其成为抽象方法(强制子类实现它)或提供一个可以被覆盖的默认实现来强制执行该方法。

1

即使这是一个旧问题,但这是我使用ReflectionMethod的解决方案:

class whale
{
  function __construct()
  {
    // some code here
  }

  function myfunc()
  {
    //Get the class name
    $name = get_called_class();

    //Create a ReflectionMethod using the class and method name
    $reflection = new \ReflectionMethod($class, 'test');

    //Call the method
    $reflection->invoke($this);
  }
}

使用ReflectionMethod类的好处是,您可以传递参数数组并检查在调用方法时哪个参数是需要的:
    //Pass a list of arguments as an associative array
    function myfunc($arguments){
      //Get the class name
      $name = get_called_class();

      //Create a ReflectionMethod using the class and method name
      $reflection = new \ReflectionMethod($class, 'test');

      //Get a list of parameters
      $parameters = $reflection->getParameters()

      //Prepare argument list
      $list = array();
      foreach($parameters as $param){

          //Get the argument name
          $name = $param->getName();
          if(!array_key_exists($name, $arguments) && !$param->isOptional())
            throw new \BadMethodCallException(sprintf('Missing parameter %s in method %s::%s!', $name, $class, $method));

          //Set parameter
          $list[$name] = $arguments[$name];
        }

      //Call the method
      $reflection->invokeArgs($this, $list);
    }

1
很简单。您可以不使用抽象类来完成此操作。
class whale
{
  function __construct()
  {
    // some code here
  }

  /*
  Child overridden this function, so child function will get called by parent. 
  I'm using this kind of techniques and working perfectly.  
  */
  function test(){
     return "";
  }

  function myfunc()
  {
    $this->test();
  }
}

class fish extends whale
{
  function __construct()
  {
    parent::construct();
  }

  function test()
  {
    echo "So you managed to call me !!";
  }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接