Есть ли хороший способ придерживаться правила ковариации/контравариантности, не создавая повторяющийся код?

#php #inheritance #covariant-return-types

#php #наследование #ковариантные типы возврата

Вопрос:

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

 class BaseFooClass {  protected $keys = [];  private $map = [];  public function __construct($keyValuePairs) {  foreach($this-gt;keys as $key =gt; $value) {  $this-gt;map[$key] = $keyValuePairs[$key] ?? null;  }  } }  class ChildFooClass1 extends BaseFooClass {  protected $keys = ['foo1_a', 'foo1_b']; }  class ChildFooClass2 extends BaseFooClass {  protected $keys = ['foo2_a', 'foo2_b', 'foo2_c']; }  //... (there are like a hundred child foo classes)  abstract class BaseBarClass {  protected $classIndex;  protected function getFooBase(int $dataIndex) : ?BaseFooClass   {  // GetRemoteData is assumed to be a global function, the important thing here is the retrieved data depends on classIndex and dataIndex  // If $classIndex is 1, the $keyValuePairs will look like ['foo1_a' =gt; value1, 'foo1_b' =gt; value2] where value1 and value2 depend on $dataIndex  $keyValuePairs = GetRemoteData($this-gt;classIndex, $dataIndex);  if (checkDataIntegrity($keyValuePairs)) {  $class = "ChildFooClass" . $this-gt;classIndex;  return new $class($keyValuePairs);  }  return null;  } }  class ChildBarClass1 extends BaseBarClass {  protected $classIndex=1;  public function getFoo(int $dataIndex) : ?ChildFooClass1   {  // this line violates covariance/contravariance rule  return $this-gt;getFooBase($dataIndex);  } }  class ChildBarClass2 extends BaseBarClass {  protected $classIndex=2;  // input to getFoo in each BarClass can be different  public function getFoo($someInput) : ?ChildFooClass2  {  $dataIndex = $this-gt;calculateDataIndex($someInput);  // this line also violates covariance/contravariance rule  return $this-gt;getFooBase($dataIndex);  } }  

Три критерия, которым я хочу соответствовать, это:

(1) Я хочу убедиться, что ChildBarClass1::getFoo возвращает только ChildFooClass1, но также убедиться, что BaseBarClass::getFooBase возвращает только класс, наследующий базовый класс. Эмпирическое правило: сделайте объявление типа как можно более строгим.

(2) Я хочу убедиться, что нет повторяющегося кода. Я не хочу вызывать GetRemoteData и checkDataIntegrity в каждой отдельной функции getFoo. На самом деле в реальном коде гораздо больше вещей, чем в этих двух.

(3) Я хочу придерживаться правила ковариации/контравариантности. В настоящее время третья последняя строка, показанная в коде, нарушает это правило, поскольку в ней используется функция, возвращающая родительский класс foo.

Кажется, я не могу придумать хорошее решение. Все, что я придумаю, либо нарушит правила, либо сделает код похожим на «взлом» и действительно уродливым. Если кто-нибудь может предложить хороший способ решить эту проблему, не нарушая ни одного из трех критериев или не изменяя общую структуру (т. Е. У каждого класса Foo есть соответствующий класс, обрабатывающий создание объекта Foo), я буду очень признателен.

Ответ №1:

Учитывая ваш код, вы можете просто удалить возвращаемый тип из getFooBase() (или с помощью php v8 сделать это : mixed ). Этот метод не является частью общедоступного api. И поэтому здесь нет никаких реальных потерь.

Поскольку getFoo() возвращаемые значения s вводятся индивидуально, вы все равно получите ошибку типа, если getFooBase() возвращаемое значение не подходит.