为什么不能在PHP中重载构造函数?

122

我已经放弃在PHP中实现构造函数重载的希望,因此我真正想知道的是为什么

这是否有原因?它会创建本质上糟糕的代码吗?不允许它是普遍接受的语言设计,还是其他语言比PHP更好些?


有一件事是确定的。缺乏像Java或C++那样的显式构造函数重载,会让你认真思考应该在参数列表中放置什么(考虑到许多人将使用函数参数的默认值来解决此问题)。 :-) - Anthony Rutledge
依赖注入难道不是一种构造函数重载吗? - clockw0rk
16个回答

172

在PHP中,你不能重载任何方法。如果你想要实例化一个PHP对象,并且希望能够传递多种不同的参数组合,可以使用工厂模式和私有构造函数。

例如:

public MyClass {
    private function __construct() {
    ...
    }

    public static function makeNewWithParameterA($paramA) {
        $obj = new MyClass(); 
        // other initialization
        return $obj;
    }

    public static function makeNewWithParametersBandC($paramB, $paramC) {
        $obj = new MyClass(); 
        // other initialization
        return $obj;
    }
}

$myObject = MyClass::makeNewWithParameterA("foo");
$anotherObject = MyClass::makeNewWithParametersBandC("bar", 3);

18
对于简洁性来说,这种方法明确表达了你想要实现的目标,对我来说更加简洁。+1 - Sam Giles
5
这是否违反了单一职责原则?你正在创建你的类(一个职责),同时还在执行各种类函数(你主要的类职责)。在现实世界的情况下,你可能需要从外部来源,比如数据库中读取一些参数,这又创造了另一个职责。 - Dennis
4
我对PHP还很陌生——之前做了很多Java:天啊,这个限制太大了!你的例子甚至没有遵循工厂模式。https://en.wikipedia.org/wiki/Factory_method_pattern - Christian Bongiorno
4
这是一个静态工厂方法。有关工厂的更一般的信息,请参见工厂[面向对象编程]。工厂是任何创建对象而不使用 new 的东西。 - J.D. Sandifer
3
正如@J.D.Sandifer所说,这是个工厂。如果有人不喜欢“静态工厂”来保持代码整洁,你总是可以创建一个MyClassFactory对象,它有makeNewWithParameterA()makeNewWithParametersBandC()方法而不是静态的,所以它们更适合于“依赖注入”框架。但如果你控制环境并且不想创建“工厂类”,那么这是最清洁的方式,就像@SamGiles所说的一样。使用相同构造函数和 switch-case 调用私有方法的解决方案是糟糕的代码。 - Xavi Montero

59

你可以使用可变参数来实现相同的效果。由于没有强类型,因此添加默认参数和所有其他“解决方案”并不太有意义。


1
这并不总是可能的,例如如果我想让一个重载是公共的,而另一个重载是私有的。 - Donald Duck

10

为了完整起见,我建议使用流畅接口(Fluent Interfaces)。这个想法是通过在你的方法末尾添加return $this;,使得你可以将调用链接在一起。因此,不需要再像下面这样:

$car1 = new Car('blue', 'RWD');
$car2 = new Car('Ford', '300hp');

(这根本行不通),你可以尝试:

$car = (new Car)
       ->setColor('blue')
       ->setMake('Ford')
       ->setDrive('FWD');

这样您就可以准确地选择要设置的属性。在很多方面,它类似于将选项数组传递到初始调用中:

$car = new Car(['make' => 'Ford', 'seats' => 5]);

我必须说,这是我最喜欢的方式,非常易读和灵活。 - Paranoid Android
9
但它并非不可变,您可以使用无效状态实例化该类。 - Pedro Amaral Couto
这会破坏 ValueObjects 的理念,而 ValueObjects 永远不应该有 setters。 - Xavi Montero
另一个问题是你不能确定是否设置了所有必须设置的变量。其次,没有编译时错误被抛出,而是必须依赖于可能发生或不发生的运行时错误来进行判断变量的使用情况。 - Mazzy
如果想在依赖注入中使用类,这是否是一种可以使用的建立初始状态的形式?此外,当您实例化的对象需要向超类/父类构造函数传递值时会发生什么? - Anthony Rutledge

6

PHP手册: 函数参数, 默认值

我通过在函数参数中使用默认值来解决这个问题。在__construct中,首先列出必填参数,在此之后以一般形式$param = null列出可选参数。

class User
{
    private $db;
    private $userInput;

    public function __construct(Database $db, array $userInput = null)
    {
        $this->db = $db;
        $this->userInput = $userInput;
    }
}

这可以被实例化为:

$user = new User($db)

或者

$user = new User($db, $inputArray);

这不是一个完美的解决方案,但我通过将参数分成绝对必要的参数和按重要性顺序列出的可选参数组来使其工作,虽然有些牵强。

它能运行。


5

在PHP中,真正的重载确实不受支持。正如@Pestilence所提到的,您可以使用可变参数。有些人只是使用各种选项的关联数组来克服这个问题。


2
<?php
//php do not automatically call parent class constructor at all if child class has constructor so you have to call parent class constructor explicitly, however parent class constructor is called automatically if child class has no constructor
class MyClass 
{
    function construct1($value1)
    {
        echo "<br/> dummy constructor is called with 1 arguments and it is $value1";
    }
    function construct2($value1,$value2)
    {
        echo "<br/> dummy constructor is called with 2 arguments and it is $value1, $value2";
    }
    function construct3($value1,$value2,$value3)
    {
        echo "<br/> dummy constructor is called with 3 arguments and it is $value1, $value2 , $value3";
    } 
    public function __construct()
    {
        $NoOfArguments = func_num_args(); //return no of arguments passed in function
        $arguments = func_get_args();
        echo "<br/> child constructor is called $NoOfArguments";
        switch ($NoOfArguments) {
            case 1:
                 self::construct1($arguments[0]);
                break;
            case 2:
                self::construct2($arguments[0],$arguments[1]);
                break;

            case 3:
                self::construct3($arguments[0],$arguments[1],$arguments[2]);
                break;

            default:
                echo "Invalid No of arguments passed";
                break;
        }
    }


}
$c = new MyClass();
$c2 = new MyClass("ankit");
$c2 = new MyClass("ankit","Jiya");
$c2 = new MyClass("ankit","Jiya","Kasish");

?>


2
他们说这项工作:
<?php
class A
{
    function __construct()
    {
        $a = func_get_args();
        $i = func_num_args();
        if (method_exists($this,$f='__construct'.$i)) {
            call_user_func_array(array($this,$f),$a);
        }
    }

    function __construct1($a1)
    {
        echo('__construct with 1 param called: '.$a1.PHP_EOL);
    }

    function __construct2($a1,$a2)
    {
        echo('__construct with 2 params called: '.$a1.','.$a2.PHP_EOL);
    }

    function __construct3($a1,$a2,$a3)
    {
        echo('__construct with 3 params called: '.$a1.','.$a2.','.$a3.PHP_EOL);
    }
}
$o = new A('sheep');
$o = new A('sheep','cat');
$o = new A('sheep','cat','dog');

// results:
// __construct with 1 param called: sheep
// __construct with 2 params called: sheep,cat
// __construct with 3 params called: sheep,cat,dog
?>

看起来每个人都对它感到满意,但对我来说却没有用...如果你让它起作用,它也会一种过载...

它会获取所有参数并将它们传递给次级函数构造器...


尝试这个的人们,请不要费心了。它不会起作用,__construct<1-3> 不扩展默认构造函数,基本上与类中的任何其他自定义函数相同。 - xorinzor
那么这在类型提示方面是如何工作的呢?目前它只关注变量的数量。 - Mazzy
@Mazzy 自从2012年以来,我就没有写过任何PHP代码,所以恐怕我得说,类型提示对我来说并不熟悉。 - Hassan Faghihi

1

您可以在构造函数中使用条件语句,然后执行您的任务。 例如:

  class Example
  {
      function __construct($no_of_args)

      {// lets assume 2
          switch($no_of_args)
          {
              case 1:
                // write your code
              break;
              case 2:
                //write your 2nd set of code
              break;
              default:
           //write your default statement
         }
      }
   }

    $object1 = new Example(1);  // this will run your 1st case
    $object2 = new Example(2);  // this will run your 2nd case

等等……


1
在这种情况下,我建议使用接口(Interfaces):
interface IExample {

    public function someMethod();

}

class oneParamConstructor implements IExample {

    public function __construct(private int $someNumber) {

    }

    public function someMethod(){

    }
}

class twoParamConstructor implements IExample {

    public function __construct(private int $someNumber, string $someString) {

    }

    public function someMethod(){

    }
}

比起在你的代码中使用,以下是更好的选择:
function doSomething(IExample $example) {

    $example->someMethod();

}

$a = new oneParamConstructor(12);
$b = new twoParamConstructor(45, "foo");

doSomething($a)
doSomething($b)

1

为了完整性起见,我会提供与当前PHP相关的翻译,因为在后续版本的PHP中,您实际上可以以某种方式重载构造函数。以下代码将有助于理解:

<?php
class A
{
    function __construct()
    {
        $a = func_get_args();
        $i = func_num_args();
        if (method_exists($this,$f='__construct'.$i)) {
            call_user_func_array(array($this,$f),$a);
        }
    }
   
    function __construct1($a1)
    {
        echo('__construct with 1 param called: '.$a1.PHP_EOL);
    }
   
    function __construct2($a1,$a2)
    {
        echo('__construct with 2 params called: '.$a1.','.$a2.PHP_EOL);
    }
   
    function __construct3($a1,$a2,$a3)
    {
        echo('__construct with 3 params called: '.$a1.','.$a2.','.$a3.PHP_EOL);
    }
}
$o = new A('sheep');
$o = new A('sheep','cat');
$o = new A('sheep','cat','dog');
?>

输出:
__construct with 1 param called: sheep
__construct with 2 params called: sheep,cat
__construct with 3 params called: sheep,cat,dog

一种非常优雅的解决方案。可能没有被编译,但对于构造函数来说它会起到作用。 - theking2

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