在 PHP 中包含必须由子类构造函数初始化的变量是不好的做法吗?

Is it bad practice to include variable which must be initialised by subclass constructor in PHP?

我阅读了大量有关构造函数和初始化变量的资料,并且遇到了一个我正在尝试解决的问题。我试图通过引入一个需要由子类初始化的变量来解决缺乏泛型支持的问题。

<?php

abstract class Collection_Base {
  protected $typeOf; // Must be initialised by the subclass
  protected $collection = array();
}

class Cookie_Collection extends Collection_Base {
  protected $typeOf = 'System\Web\Http_Cookie';

  public function set ($item) {
    if (!$item instanceof $this->typeOf) {
      throw new \InvalidArgumentException;
    }
    $this->collection[] = $item;
  }
}

?>

所以我想知道,在 PHP 中包含必须由子类构造函数初始化的变量是不是不好的做法?这样做有什么需要注意的吗?

虽然没有直接关系,但我使用了以下来源来收集我的信息:


解决方案

<?php

abstract class Collection_Base {
  protected $collection = array();

  public abstract function getType();

  private function getTypeInternal () {
    $type = $this->getType();

    if (is_class($type)) {
      throw new \UnexpectedValueException;
    }

    return $type;
  }

  public function set ($item) {
    $type = $this->getTypeInternal();
    if (!$item instanceof $type) {
      throw new \InvalidArgumentException;
    }
    $this->collection[] = $item;
  }
}

class Cookie_Collection extends Collection_Base {
  protected $type = 'System\Web\Http_Cookie';

  public function getType () {
    return $this->type;
  }
}

?>

使用工厂模式隐藏构造函数的详细信息...参见http://www.phptherightway.com/pages/Design-Patterns.html

使 Collection_Base 成为一个抽象 class 并定义一个 returns 适合 class 名称的方法:

abstract class Collection_Base
{
    protected $collection = [];

    public function add($item)
    {
        if (!$item instanceof $this->getType()) {
            throw new \InvalidArgumentException();
        }

        $this->collection[] = $item;
    }

    abstract protected function getType();
}

class Collection_Cookie extends Collection_Base
{
    protected function getType()
    {
        return Configuration_Element::class;
    }
}

使用这种方法,其他开发人员不可能忘记 type "property"。


编辑:

按照 Luca Rocchi 的建议使用工厂也是一个很好的主意。

我以为我认为这是一种反模式,所以我寻找我读到它的地方,但后来我记得是这样的:http://en.wikipedia.org/wiki/Call_super,这不是一回事。

关于你在做什么。有很多类似的库使用这样的做法,但是它们的不同之处在于以抽象方法的方式强制执行这种做法:

abstract class Collection_Base {
    protected $typeOf;

    protected $collection = array();

    /**
     * @return string
     */
    public function getType()
    {
        if (null === $this->typeOf) {
            $this->typeOf = $this->doGetType();
        }

        return $this->typeOf;
    }

    /**
     * @return string
     */
    abstract protected function doGetType();
}

class Cookie_Collection extends Collection_Base {

    /**
     * @param $item
     */
    public function set ($item) {
        if (!$item instanceof $this->getType()) {
            throw new \InvalidArgumentException;
        }
        $this->collection[] = $item;
    }

    /**
     * @inheritdoc
     */
    protected function doGetType()
    {
        return 'System\Configuration\Configuration_Element';
    }
}