Проверка наследования признаков PHP

Итак, я, наконец, добрался до игры с чертами, и они очень удобны, проблема, с которой я столкнулся, заключается в том, что я хочу иметь некоторые черты, чтобы добавить функциональность к моим объектам данных. Само по себе это просто, за исключением того, что при этом я использую методы, определенные в моем базовом объекте данных.

abstract class Base_Object {
  protected function _addToUpdate($field, $value) {
    ...
  }
  ...
}

trait Extended_Object {
  public function doSomeStuff() {
    ...
    $this->_addToUpdate($someVar, $someOtherVar);
  }
  ...
}

class User extends Base_Object {
  use Extended_Object;
  ...
}

Моя проблема заключается в том, что если кто-то еще в моей команде решит использовать черту Extended_Object для объекта, который не расширяет черту Base_Object. Я думал о том, чтобы поставить галочку в методе _addToUpdate, но в идеале я хотел бы, чтобы при создании экземпляра отображалась ошибка.

Я придумал решение, которое работает, но заставляет меня чувствовать себя немного грязным и далеким от идеала.

abstract class Base_Object {
  protected function _addToUpdate($field, $value) {
    ...
  }
  ...
}

trait Extended_Object {
  abstract protected function _addToUpdate($field, $value);

  public function doSomeStuff() {
    ...
    $this->_addToUpdate($someVar, $someOtherVar);
  }
  ...
}

class User extends Base_Object {
  use Extended_Object;
  ...
}

Добавляя абстрактный метод к Extended_Object, я могу, по крайней мере, быть уверенным, что будет отображаться ошибка, если метод, который мне нужен в Base_Object, отсутствует, но я не могу гарантировать, что рассматриваемый метод действительно будет делать то, что я хочу.

В идеале я хотел бы иметь возможность запускать что-то вроде кода ниже, когда объект создается с использованием признака Extended_Object

if (!($this instanceof Base_Object)) {
  throw new Inheritance_Exception("Base_Object");
}

Я надеюсь, что кто-то нашел способ сделать это или, по крайней мере, лучшее решение, чем мое.

Примечание: я знаю, что могу сделать это с помощью конструктора, но это будет жизнеспособно только при использовании одного трейта, если я решу позже создать еще несколько трейтов расширения объекта, это станет очень грязным очень быстро

Редактировать: я понимаю, что трейты на самом деле не предназначены для того, что я пытаюсь сделать, но они хотя бы частично позволяют мне обойти проблему одиночного наследования, и я знаю, что я не единственный разработчик, планирующий это сделать. использовать их таким образом


person Matt Indeedhat Holmes    schedule 04.06.2014    source источник
comment
У меня отношения любви и ненависти к трейтам, если бы я мог переименовывать как атрибуты, так и методы (возможно, используя один и тот же трейт более одного раза в одном и том же классе), и в любой момент, а не только когда есть конфликт, тогда черты бы потрясли. Однако, даже если рассматривать их как не более чем недоработанную попытку «горизонтального наследования» (композиции), их все же стоит использовать.   -  person user5321531    schedule 01.08.2014


Ответы (4)


Ну (ИМХО) существует проблема с тем, как вы используете трейты, что технически является анти-шаблоном.

Во-первых, что такое черты и почему их следует использовать. Цитирую один из комментарии в php.net

Лучший способ понять, что такое черты и как их использовать, — это посмотреть на них с точки зрения того, чем они по сути являются: копирование и вставка с помощью языка.

Если вы можете копировать и вставлять код из одного класса в другой (а мы все это делали, хотя и стараемся не делать этого из-за дублирования кода), то у вас есть кандидат на трейт.

Даже то, что трейты позволяют вам использовать членов класса, который их использует, не означает, что вы должны это делать. вернуться к SRP, это настоящее нарушение. PHP позволяет вам делать echo $_POST['XSS data'], но это не значит, что вы должны это делать. Таким образом, независимо от того, как вы хотите ограничить использование свойств, на самом деле вы представляете проблему и пытаетесь ее решить, поэтому, чтобы ответить на ваш вопрос, просто переделайте свой код. чтобы вы не использовали методы и члены класса, исходя из предположения, что каждый класс, который будет использовать трейт, должен иметь эти методы и члены.

person mamdouh alramadan    schedule 04.06.2014

Я думаю, что ваше решение с abstract function как раз то, для чего предназначено это средство. Я согласен с твоим внутренним ощущением:

мне не гарантируется, что рассматриваемый метод действительно будет делать то, что я хочу

Однако это то же самое предположение, которое вы делаете всякий раз, когда используете interface: единственное, что утверждается, это то, что метод существует с правильной подписью (которая в PHP в основном сводится к его имени и количеству параметров). Нет никакого способа узнать, действительно ли функция ведет себя особенно полезным образом при заданных параметрах.

В случае trait абстрактный метод фактически представляет собой тот же тип контракта, что и interface: «чтобы use это trait, вы должны предоставить эти предварительные условия».

Я также хотел бы отметить, что общее описание traits - это «автоматическое копирование и вставка», поэтому его обычно следует рассматривать как отдельное от иерархий объектов. С технической точки зрения это представляет собой «горизонтальное повторное использование кода», а не «множественное наследование». Например, вы можете определить trait, который разрешает переход от class к implement и interface, но для внешнего кода важен именно interface, а не trait.

person IMSoP    schedule 04.06.2014

Я столкнулся с аналогичной проблемой, но с несколькими подклассами, унаследованными от базового класса.

На этом этапе имеет смысл поместить абстрактные объявления трейтов в какой-то включаемый файл и как сам трейт, например:

abstract class Base_Object {

  use BaseObjectTrait;
  ...
}

trait BaseObjectTrait {

  protected function _addToUpdate($field, $value) {
    ...
  }

}

trait BaseObjectTraitPrototypes {

  abstract protected function _addToUpdate($field, $value);

}

trait Extended_Object {

  use BaseObjectTraitPrototypes;

  public function doSomeStuff() {
    ...
    $this->_addToUpdate($someVar, $someOtherVar);
  }
  ...
}

class User extends Base_Object {
  use Extended_Object;
  ...
}

Может ли кто-нибудь придумать более элегантное решение, чем это? Я использую здесь термин «прототип» очень вольно, исходя из смысла программирования на C. Файлы «заголовков» также взяты из контекста программирования C, но все же не идеальны. Может ли кто-нибудь придумать более краткую концепцию для проблемной области, возможно, что-то приемлемое для php-fig?

person user5321531    schedule 01.08.2014
comment
Отвечая на мой собственный вопрос здесь, как насчет добавления суффикса «Интерфейс» к признаку, чтобы назвать признак, который объявляет абстрактные определения характеристик признака, а затем абстрактные объявления включаются в признаки подклассов (например, в приведенном выше примере BaseObjectTraitPrototypes станет BaseObjectTraitInterface). В качестве решения это все еще не кажется на 100% правильным, но я думаю, что оно движется в правильном направлении. Автоматическая генерация интерфейса с помощью IDE была бы бонусом. - person user5321531; 01.08.2014
comment
В качестве альтернативы BaseObjectPrototype - файл прототипа, который может быть реализован как трейт (use ...) или, возможно, как простой файл include ..., объявляющий прототипы не только для любого трейт-файла, связанного с классом, но также для любых методов и свойств в самом классе. Прямой файл include ... был бы более семантически правильным с точки зрения самого языка PHP. - person user5321531; 02.08.2014
comment
однако с несколькими подклассами, унаследованными от базового класса. - просто любопытно, что касается вашей проблемы ... вы предполагаете, что ваша черта вызывает методы из определенного подкласса? А потом пытаетесь использовать эту черту в других (горизонтальных) подклассах? - person MrWhite; 18.05.2016

Моя проблема заключается в том, что если кто-то еще в моей команде решит использовать черту Extended_Object для объекта, который не расширяет черту Base_Object.

:

В идеале я хотел бы иметь возможность запускать что-то вроде приведенного ниже кода, когда объект создается с использованием черты Extended_Object.

if (!($this instanceof Base_Object)) {
    throw new Inheritance_Exception("Base_Object");
}

Может быть, я что-то упускаю, но если эта черта должна применяться только к экземплярам Base_Object, то звучит так, как будто эта «черта» должна быть подклассом Base_Object, а не чертой вообще?

Затем класс User расширяет этот Extended_Object (подкласс), а не use создает Extended_Object (признак).

Откуда: http://php.net/manual/en/language.oop5.traits.php

Черта предназначена для уменьшения некоторых ограничений одиночного наследования, позволяя разработчику свободно повторно использовать наборы методов в нескольких независимых классах, находящихся в разных иерархиях классов.

person MrWhite    schedule 18.05.2016