Почему PHP допускает «несовместимые» конструкторы?

Вот несколько фрагментов:

  1. Метод переопределения конструктора имеет дополнительный параметр.

    class Cat { function __construct() {} } class Lion extends Cat { function __construct($param) {} } 
  2. Переопределяющий (обычный) метод имеет дополнительный параметр.

     class Cat { function doSomething() {} } class Lion extends Cat { function doSomething($param) {} } 

Первый будет работать, в то время как второй будет бросать Declaration of Lion::doSomething() should be compatible with that of Cat::doSomething() .

Почему особое отношение к конструкторским методам?

Solutions Collecting From Web of "Почему PHP допускает «несовместимые» конструкторы?"

Чтобы понять, почему к ним относятся по-разному, вы должны понимать Принцип замещения Лискова , который гласит:

Если для каждого объекта o1 типа S существует объект o2 типа T такой, что для всех программ P, определенных в терминах T, поведение P не изменяется, когда o1 заменяется на o2, тогда S является подтипом T. " БарбараЛисков, абстракция данных и иерархия, извещения SIGPLAN, 23,5 (май 1988 г.).

В двух словах это означает, что любой класс, использующий ваш Lion или Cat должен надежно называть doSomething на нем, независимо от того, какой класс один или другой. Если вы измените подпись метода, это больше не будет гарантировано (вы можете расширить его, но не сузить его).

Очень простой пример

 public function doSomethingWithFeline(Cat $feline) { $feline->doSomething(42); } 

Поскольку Lion extends Cat , вы установили отношения is-a, что означает doSomethingWithFeline примет Lion для Cat . Теперь представьте, что вы добавили требуемый аргумент doSomething в Lion . Вышеприведенный код сломается, потому что он не передает этот новый параметр. Следовательно, необходимость в совместимых подписях.

LSP не применяется к конструкторам , потому что подтипы могут иметь разные зависимости . Например, если у вас есть FileLogger и DBLogger, для ctors (конструкторов) первого потребуется имя файла, в то время как для последнего потребуется адаптер db. Таким образом, ctors – это конкретные реализации, а не часть контракта между классами.

Принцип замещения Лискова гласит, что «если S является подтипом T, то объекты типа T могут быть заменены объектами типа S». В вашем примере это означает, что вы должны иметь возможность заменить объекты типа Cat объектами типа Lion .

Это причина, по которой ваш второй код не разрешен. Вы больше не сможете выполнять эту подстановку, так как вы больше не сможете вызывать метод ->doSomething() без аргументов.

Конструктор, с другой стороны, не подчиняется Принципу подстановки Лискова, поскольку он не является частью результирующего API объекта. Вы все равно сможете заменить полученные объекты, независимо от того, соответствуют ли подписи конструктора.

На самом деле довольно распространено, что подклассы имеют больше аргументов конструктора, потому что они более специфичны и нуждаются в большем количестве зависимостей.

__construct() поскольку они уникальны для каждого класса. Конструктор для Lion не является тем же самым конструктором для Cat , хотя, если Lion extends Cat и Lion не имеют __construct() , вы все равно можете расширить родительский __construct() с помощью Lion::__construct() .

В отличие от других методов, PHP не будет генерировать сообщение об ошибке уровня E_STRICT, когда __construct () переопределяется с разными параметрами, чем у родительского метода __construct ().

Руководство PHP: конструкторы и деструкторы

Другие магические методы принимают конкретные аргументы, что означает, что их аргументы и т. Д. Всегда будут согласованными.

После того, как классы создаются, тогда полиморфизм / переопределение для вашего doSomething() . Ваш родительский класс, например, как абстрактный класс, определяет аргументы и видимость, которые должны соответствовать дочернему классу для поддержки переопределения.

Конструктор предназначен только для конкретного объекта. Это порождает его.

Другие методы – до тех пор, пока этот подтип связан с унаследованным классом, поэтому эти методы являются частью одного и того же объекта и поэтому должны быть совместимы при распространении по определению нескольких классов.

В противном случае вы создадите объект, который немного шизо.


Изменить: если вы также хотите ввести строгие проверки с помощью сигнатуры конструктора, вы можете использовать interface с PHP 5.2, который добавляет конструктор:

Добавлена ​​поддержка конструкторов в интерфейсах для принудительной проверки подписей конструктора в реализациях. Начиная с PHP 5.2.0, интерфейсы могут иметь конструкторы. Однако, если вы решите объявить конструктор в интерфейсе, каждый класс, реализующий этот интерфейс, ДОЛЖЕН включать конструктор с подписями, сопоставляемый с конструктором базового интерфейса. Под «подписями» мы понимаем определения типа и возвращаемого типа, включая подсказки типа и включаем, передаются ли данные по ссылке или по значению.

(взять из: Другие улучшения – Перенос с PHP 5.1.x на PHP 5.2.x )

То, что вы описываете, – это перегрузка, которая не поддерживается PHP. Теперь, когда вы создаете конструктор для класса, он используется только в этом классе, а родительский constuctor не вызывается по умолчанию (см. « Конструкторы и деструкторы» . Его нужно вызвать вручную parent::__construct() .

Когда вы создаете метод в своем классе, он напрямую обращается к родительскому методу.

Итак, в заключение:

  • constuctor не использует перегрузку, а только для самого класса
  • метод использует перегрузку и поэтому не допускается