【问题标题】:Is what seems like polymorphism in PHP really polymorphism?PHP中看起来像多态的东西真的是多态吗?
【发布时间】:2009-04-14 23:30:09
【问题描述】:

试图弄清楚 PHP 是否支持方法重载、继承和多态等特性,我发现:

  • 不支持方法重载
  • 确实支持继承

但我不确定多态性。我在网上搜索到了这个:

我应该注意到,在 PHP 多态性不是它的方式 应该。我的意思是它确实有效, 但由于我们有一个弱数据类型,它的 不正确。

那么真的是多态吗?

编辑 只是不能在PHP supports polymorphism 旁边放置一个明确的“是”或“否”。我不愿意说:“PHP 不支持多态性”,而实际上它确实支持。反之亦然。

【问题讨论】:

  • "PHP 支持多态,但有一些限制。"多田。 :)
  • 谢谢 :) 支持您的回答。
  • 为什么这个问题与语言无关?对我来说似乎完全是关于 PHP 的。
  • @beldaz hehe :-) 这只是我的英语。在我问这个问题的时候,我认为不可知论意味着别的......我现在知道了:-) :-D 但是,pssssst,不要告诉任何人 ;-)

标签: php oop programming-languages polymorphism


【解决方案1】:
class Animal {
    var $name;
    function __construct($name) {
        $this->name = $name;
    }
}

class Dog extends Animal {
    function speak() {
        return "Woof, woof!";
    }
}

class Cat extends Animal {
    function speak() {
        return "Meow...";
    }
}

$animals = array(new Dog('Skip'), new Cat('Snowball'));

foreach($animals as $animal) {
    print $animal->name . " says: " . $animal->speak() . '<br>';
}

你可以随心所欲地给它贴上任何标签,但这对我来说就像是多态性。

【讨论】:

  • 当然,但是 Animal 接口没有指定会有 speak() 方法。所以我可以构造一个像 new FPDF(); 这样的对象。然后将其添加到动物数组中。然后怎样呢?它会怎么说话()?
  • 可以在Animal类中指定一个speak方法,如果子类没有指定自己的speak,就会调用该方法。如果是这样,您仍然可以使用 parent::speak(); 调用它
  • 所以你会说它确实支持多态性。我正在考虑将其称为“不安全”或“弱多态行为”。你有什么看法?
  • 不安全和虚弱不是我会使用的词,老实说。正如 Till 所示,PHP 也支持方法重载,但它并不像应有的那样干净。我会说它具有多态性,考虑到所有因素。
  • @PeterPerháč 我迟到了讨论,但我不会使用弱、不安全、不那么干净或任何其他限定词。所有这些都是相对术语,这就引出了“相对于什么?”的问题。它是 PHP,您可以将这个编码概念应用到您的 PHP 代码中。就是这样。在 PHP 中实现这一概念与在 C++ 中不同——实际上使用这两种语言的所有其他方面也是如此。它们是不同的语言,所以当然是不同的。更弱、更简洁或不太安全意味着只有 C++ 具有“真正的多态性”,但显然情况并非如此。
【解决方案2】:

虽然 PHP 不支持您在其他语言(例如 Java)中体验过的方法重载。但是您可以在 PHP 中进行方法重载,但定义方法不同。 如果你想为给定的方法提供不同的功能,在 PHP 中使用不同的参数集,你可以这样做:

class myClass {
    public function overloadedMethod() {
        // func_num_args() is a build-in function that returns an Integer.
        // the number of parameters passed to the method.
        if ( func_num_args() > 1 ) {
            $param1 = func_get_arg(0);
            $param2 = func_get_arg(1);
            $this->_overloadedMethodImplementation2($param1,$param2)
        } else {
            $param1 = func_get_arg(0);
            $this->_overloadedMethodImplementation1($param1)
        }
    }

    protected function _overloadedMethodImplementation1($param1) {
        // code 1
    }

    protected function _overloadedMethodImplementation2($param1,$param2) {
        // code 2
    }
}

可能会有更简洁的实现,但这只是一个示例。

PHP 支持继承和接口。所以你可以使用它们进行多态性。你可以有这样的界面:

// file: MyBackupInterface.php
interface MyBackupInterface {
    // saves the data on a reliable storage
    public function saveData();
    public function setData();
}

// file: myBackupAbstract.php
require_once 'MyBackupInterface.php';
class MyBackupAbstract implements MyBackupInterface {
     protected $_data;
     public function setData($data) {
         $this->_data= $data;
     }
     // there is no abstract modifier in PHP. so le'ts avoid this class to be used in other ways
     public function __construct() {
          throw new Exception('this class is abstract. you can not instantiate it');
     }
}

// file: BackupToDisk.php
require_once 'MyBackupAbstract.php';
class BackupToDisk extends MyBackupAbstract {
     protected $_savePath;

     // implement other methods ...

     public function saveData() {
           // file_put_contents() is a built-in function to save a string into a file.
           file_put_contents($this->_savePath, $this->_data);
     }
}

// file: BackupToWebService.php
require_once 'MyBackupAbstract.php';
class BackupToWebService extends MyBackupAbstract {
     protected $_webService;
     // implement other methods ...

     public function saveData() {
           // suppose sendData() is implemented in the class
           $this->sendData($this->_data);
     }
}

现在在您的应用程序中,您可以像这样使用它:

// file: saveMyData.php
// some code to populate $myData
$backupSolutions = array( new BackupToDisk('/tmp/backup') , new BackupToWebService('webserviceURL') );
foreach ( $backupSolutions as $bs ) {
    $bs->setData($myData);
    $bs->saveData();
}

你是对的,PHP 不是强类型语言,我们从未提到你的任何 $backupSolutions 将是“MyBackupAbstract”或“MyBackupInterface”,但这不会阻止我们具有不同功能的多态性的本质使用相同的方法。

【讨论】:

    【解决方案3】:

    PHP 具有基于类的多态性,但缺乏实现基于参数的多态性的正式机制。

    基于类的多态性意味着您可以根据基类进行思考,并且调用的方法取决于最终类。例如,如果您有一个包含各种类(如 Triangle 和 Circle)的对象的数组,并且这些类中的每一个都扩展了同一个类 Shape,那么您可以将您的数组视为仅仅是形状的集合。您可以遍历形状并调用每个形状的 getArea() 方法。多态性是调用 getArea() 方法取决于对象的类的现象。如果您的形状是三角形,则调用 Triangle::getArea(),如果是圆形,则调用 Circle::getArea()——即使您的代码不区分圆形和三角形,而是将每个对象视为只是一个形状。同一行代码会导致执行不同的代码块,具体取决于对象的类。

    基于参数的多态性是一些强类型语言的特性,其中可以在一个类中定义多个同名方法,只要它们具有不同的参数;那么调用哪个方法取决于提供的参数。您可以通过手动考虑方法中的参数类型来模拟弱类型语言(如 PHP)中基于参数的多态性。尽管 JavaScript 缺乏基于参数的原生多态性,但 jQuery 为实现多态 API 所做的工作。

    因此,如果“支持多态”是指它提供了一种正式的机制来实现基于参数的多态,那么答案是否定的。对于任何更广泛的解释,答案是肯定的。按理说,基于类的多态现象出现在每一种面向对象语言中;对于一种执行隐式类型转换的语言来实现基于参数的多态性是没有意义的。

    【讨论】:

      【解决方案4】:

      __call()__callStatic() 应该支持方法重载。更多信息请参见manual。或者你到底在追求什么?

      更新:我刚刚注意到其他回复。

      对于另一种重载方法的方法,请考虑以下几点:

      <?php
      public function foo()
      {
          $args = func_get_arg();
      }
      

      当然不漂亮,但它让你几乎可以做任何你想做的事情。

      【讨论】:

      • 工具箱? :) 呵呵...是的,请记住,不要使用它。另外,如果你在猴子补丁之后,请查看 runkit。
      • “Homer's Tule Box”是 Homer Simpson 在他的工具箱上写的。 :)
      【解决方案5】:

      你仍然可以覆盖方法,只是不要重载它们。重载(在 C++ 中)是指对多个方法使用相同的方法名称,只是参数的数量和类型不同。这在 PHP 中会很困难,因为它是弱类型的。

      覆盖是子类替换基类中的方法的地方。这确实是多态性的基础,您可以在 PHP 中做到这一点。

      【讨论】:

      • 对不起,这不是我的意思。我明白为什么 PHP 不支持方法重载,以及重载和覆盖之间的区别。但是我可以宣称它确实支持多态性吗?
      • 我们可以在 PHP 中实现方法重载,方法是给出我在回答中说明的错觉。
      【解决方案6】:

      有人称之为duck typing

      【讨论】:

        【解决方案7】:

        PHP 允许在其他语言中产生编译错误的多态代码。一个简单的例子说明了这一点。第一个产生预期编译错误的 C++ 代码:

        class Base {};
        
        class CommonDerivedBase {
           public:
            // The "= 0" makes the method and class abstract
            // virtual means polymorphic method
            virtual whoami() = 0;
        };
        
        class DerivedBase : public CommonDerivedBase {
           public:    
             void  whoami() { cout << "I am DerivedBase \n"; }
        };
        
        class Derived1 : public CommonDerivedBase {
          public:
            void  whoami() { cout << "I am Derived1\n"; }
        };
        
        class Derived2 : public CommonDerivedBase {
        public:
         void whoami() { cout <<  "I am Derived2\n"; }
        };
        
        /* This will not compile */
        void  test_error(Base& db)
        {
           db.whoami();
        }
        

        C++ 编译器将针对行db.whoami() 发出此错误消息

        error: no member named 'whoami' in 'Base'
        

        因为 Base 没有名为 whoami() 的方法。然而,类似的 PHP 代码直到运行时才发现此类错误。

        class Base {}
        
        abstract class DerivedCommonBase {
          abstract function whoami();
        }
        
        class Derived1 extends DerivedCommonBase {
        
         public function whoami() { echo "I am Derived1\n"; }
        }
        
        class Derived2 extends DerivedCommonBase {
        
         public function whoami() { echo "I am Derived2\n"; }
        }
        
        /* In PHP, test(Base $b) does not give a runtime error, as long as the object 
         * passed at run time derives from Base and implements whoami().
         */
        function test(Base $b)
        {
          $b->whoami(); 
        }
        
        $b = new Base();
        $d1 = new Derived1();
        $d2 = new Derived2();
        
        $a = array();
        
        $a[] = $d1;
        $a[] = $d2;
        
        foreach($a as $x) {
        
          echo  test($x);
        }
        
        test($d1);
        test($d2);
        test($b); //<-- A run time error will result.
        

        foreach 循环与输出一起工作

        I am Derived1
        I am Derived2
        

        在您调用 test($b) 并传递 Base 实例之前,您不会收到运行时错误。所以在foreach之后,输出会是

        I am Derived1
        I am Derived2
        PHP Fatal error:  Call to undefined method Base::whoami() in
        home/kurt/public_html/spl/observer/test.php on line 22
        

        关于使 PHP 更安全的唯一方法是添加运行时检查 测试 $b 是否是您想要的类的实例。

        function test(Base $b)
        {
          if ($b instanceof DerivedCommonBase) {
             $b->whoami(); 
          } 
        }
        

        但多态性的全部意义在于消除此类运行时检查。

        【讨论】:

        • 我真的不明白你的意思。您是在说“如果我在一个既不实现该方法也不实现或扩展任何具有该方法的接口或类的类上调用一个方法,我会得到一个在编译时未捕获的错误。”代码$myVariable = "abc"; $myVariable-&gt;MyTestMethod(); 也可以这样说。当然,如果您在完全不相关的对象上调用随机函数,它不会运行,这是合乎逻辑的。直到运行时才发现它的事实是因为 PHP 的动态和弱类型。因此,您展示了一个完全错误的多态性尝试。
        • 为了证明我的观点,下面是代码的结果:` $myVariable = "abc"; $myVariable->MyTestMethod();` --- Fatal error: Call to a member function MyTestMethod() on a non-object in ...\tests\test2.php on line 5 所以是的,我看不出有什么意外......
        【解决方案8】:

        多态可以通过以下方法实现:

        1. method overriding - 正常漂亮如上

        2. method overloading

        你可以通过神奇的方法__call()创造一种方法重载的错觉:

        class Poly {
            function __call($method, $arguments) {
                if ($method == 'edit') {
                    if (count($arguments) == 1) {
        
                        return call_user_func_array(array($this,'edit1'), $arguments);
                    } else if (count($arguments) == 2) {
                        return call_user_func_array(array($this,'edit2'), $arguments);
                    }
                }
            }
            function edit1($x) {
                echo "edit with (1) parameter";
            }
        
            function edit2($x, $y) {
                echo "edit with (2) parameter";
            }
        
        }
        
        $profile = new Poly();
        $profile->edit(1);
        $profile->edit(1,2);
        

        解释:

        1) Here we are utilizing the power of __call() of listening calls of 
           non-available     methods and 
        2) after knowing it who had called with their inputs diverting them to desired 
           method 
        

        In php,我们其实是working under the hood给了想要的行为,给了感觉of method overloading

        【讨论】:

          【解决方案9】:

          我在这里看到的 php 不支持多态性,也不支持方法重载。您可以破解自己的方式来实际接近这两个 oop 功能,但它们与它的最初目的相去甚远。这里的许多示例要么是扩展一个类,要么是创建一个 hack 来模拟多态性。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-07-23
            • 2016-11-14
            • 1970-01-01
            • 2013-07-24
            • 2015-02-09
            • 1970-01-01
            • 2012-01-11
            • 1970-01-01
            相关资源
            最近更新 更多