PHP ООП дизайн - ограничение параметров конкретными дочерними классами при реализации общих интерфейсов

Я часто делаю PHP-проекты, направленные на соскабливание иерархических данных с веб-страниц и сохранение их в БД (по сути, структурирование данных - подумайте о соскабливании правительственных веб-сайтов, которые имеют данные, но не предоставляют их в структурированном виде). Каждый раз я пытаюсь придумать ООП-дизайн, который позволил бы мне достичь следующего:

  • Легко заменить текущие скрипты разбора HTML на новые, в случае изменения исходной веб-страницы
  • Позволить легко расширять собранные и сохраненные данные, поскольку эти проекты предназначены для других людей. Моя цель - собрать "базовые" данные, в то время как другие могут решить включить что-то дополнительное, изменить способ сохранения и т.д.

Пока я еще не нашел решения, но ближе всего я подошел к этому примерно так:

Я определяю абстрактный класс для контейнеров данных, который будет реализовывать общие функции просмотра деревьев:

abstract class DataContainer {

  protected $parent = NULL;
  protected $children = NULL;   

  public function getParent() {
    return $this->parent;
  }

  public function getChildren() {
    return $this->children;
  }             
}

А затем у меня есть реальные контейнеры данных. Представьте себе, что я собираю данные об участии в парламентских сессиях вплоть до уровня "конкретный вопрос на заседании". У меня будет SessionContainer, SittingContainer, QuestionContainer, которые будут расширять DataContainer.

Каждый из данных сессии, сидения и вопроса берется из разных URL. Оставив в стороне механизм получения содержимого URL, предположим, что мне нужны классы скребков, которые будут принимать контейнеры и DOmDocument для фактического разбора. Поэтому я бы определил общий интерфейс следующим образом:

interface Scraper {
  public function scrapeData(DOMDocument $Dom, DataContainer $DataContainer);   
}

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

class SessionScraper implements Scraper {
  public function scrapeData(DOMDocument $DOM, SessionContainer $DataContainer) {
  }
}

Наконец, у меня будет общий класс Factory, который также реализует интерфейс Scraper и просто распределяет скрепы по соответствующим скрепам. Вот так:

public function scrapeData(DOMDocument $DOM, DataContainer $DataContainer) {
  //get the scraper from configuration array
  $class = $this->config[get_class($DataContainer)];
  $craper = new $class();
  $class->scrapeData($DOM, $DataContainer);
}

Это класс, который будет фактически вызываться в коде. Очень похожим образом я мог бы поступить с сохранением в БД - каждый контейнер данных мог бы иметь свой класс DBSaver, который бы реализовывал интерфейс DBSaver. Опять же, все вызовы могут быть сделаны через класс Factory, который также реализует интерфейс DBSaver.

Все было бы прекрасно, но проблема в том, что классы, реализующие интерфейс, должны реализовывать точную сигнатуру интерфейса. Например, метод SessionScraper::scrapeData не может принимать только объекты SessionContainer, он должен принимать все объекты DataContainer. Но это не так!

Наконец, вопрос:

  • Может быть, мой дизайн неправильный, и я должен структурировать все совершенно по-другому? (как?), или:
  • Моя конструкция в порядке, просто мне нужно обеспечить соответствие типов внутри методов с помощью instanceof и подобных проверок вместо того, чтобы обеспечивать его с помощью typehinting?

Заранее спасибо за все предложения/критику. Я абсолютно счастлив, если кто-то опрокинет этот код на голову, если это необходимо!

6
задан Aurimas 6 October 2011 в 21:40
поделиться