永远不要在构造函数中实例化对象


Never instantiate objects in the constructor?

是否像下面这样在构造函数中实例化对象?

class Foo
{
    public function fooMethod() {
        return 'foo method';
    } 
}
class Too
{
    public function tooMethod() {
        return 'too method';
    } 
}
class Boo
{
    public $foo;
    public $too;
    public function __construct()
    {
        $this->foo = new Foo();
        $this->too = new Too();
    }
}

如果是,能有多糟?应该怎样做才合适呢?

手动在另一个类中实例化类创建隐式依赖关系,这很难维护-如果您需要更改那些FooToo类,您将很难检测需要更改的内容。

所以,一个更好的管理依赖的方法是:
class Foo
{
    private $_bar;
    function __construct(Bar $bar)
    {
         $this->_bar = $bar;
    }
}

这样,你的对象依赖关系是显式的。这样做的另一个好处是,一些PHP框架(Laravel、Zend、Symfony)允许自动解析依赖项。这意味着,你不需要手动实例化你的对象,只需要通过某种工厂来实例化——就像这样(Laravel):

$foo = App::make('Foo');

一个App工厂自动检测你的Foo类依赖与一些反射魔法,并适当地注入它们。其他框架也有类似的功能。

同样,在OOP中有一些通用原则,称为SOLID,它有助于开发更好的OOP设计。其中一个——D,代表Dependency Inversion。它的意思是,您应该避免hard依赖,就像在您的代码中一样。相反,FooBar类都应该依赖于interface,像这样:

interface BarInterface
{
    function doBar();
}
class Bar implements BarInterface
{
    function doBar()
    {
        print "Doing BAR";
    }
}
class Foo
{
    /**
     * @var BarInterface
     */
    private $bar;
    function __construct(BarInterface $bar)
    {
        $this->bar = $bar;
    }
}

现在,如果你需要用其他东西来改变Bar类,如果你的替代品也实现了BarInterface,那么所有的地狱都不会破裂。

这本身并不坏。

缺点是它降低了类的"可测试性",仅仅因为Boo现在依赖于存在的FooToo

这取决于项目的大小。

对于大型项目或长期项目,应该稍微改变一下。

理想情况下,你应该重构它,实现依赖注入模式,也许使用一个工厂来实例化它。

示例代码:

interface FooInterface { function fooMethod(); } 
class Foo implements FooInterface { function fooMethod(){return 'Foo';} }
interface TooInterface { function tooMethod(); } 
class Too implements FooInterface { function tooMethod(){return 'Too';} }
class Boo
{
    public $foo;
    public $too;
    public function __construct(FooInterface $foo, TooInterface $boo)
    {
        $this->foo = $foo;
        $this->too = $boo;
    }
}
class BooFactory
{
    public static function create()
    {
        return new Boo(new Foo, new Too);
    }
}

这取决于您的需求和类。假设每次调用Foo/Too的构造函数,你都会对数据库执行一个巨大的查询来获取数据,在这种情况下,我会选择使用延迟实例化。

当然,在构造函数中初始化属性是一个好做法,但在实际的性能中可能是你的敌人。

的例子:

class Boo {
   private $foo = null;
   private $too = null;
   public function __construct() {
      //Do something else
   }
   public function getFoo() {
      if (is_null($this->foo)) {
         $this->foo = new Foo();
      }
      return $this->foo;
   }
   public function getToo() {
      if (is_null($this->too)) {
         $this->too = new Too();
      }
      return $this->too;
   }
   public function aMethodThatUsesFoo() {
      $foo = $this->getFoo();
      $foo->fooMethod();
   }
   public function aMethodThatDoesntUsesFoo() {
      echo "Hello!, I don't need foo or too to execute this method";
   }
}

如果你只使用这个类来执行aMethodThatDoesntUsesFoo(),它将永远不会调用Foo/Too的构造函数。

$boo = new Boo();
$boo->aMethodThatDoesntUsesFoo();

如果你只执行aMethodThatUsesFoo(),它将只实例化Foo

$boo = new Boo();
$boo->aMethodThatUsesFoo();