PHP OOP design - ограничение параметров конкретными дочерними классами при реализации универсальных интерфейсов - PullRequest
6 голосов
/ 07 октября 2011

Я часто делаю проекты 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 и аналогичными проверками, а не принудительно вводить их с помощью набора текста?

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

1 Ответ

2 голосов
/ 07 октября 2011

Container прыгает в глаз.Это имя очень общее, вам может понадобиться что-то более динамичное.Я думаю, что у вас есть Data, а у вас classify, поэтому у него есть type.

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

Если бы теперь каждый Container имел бы type, Scraper мог бы сигнализировать / сказать, применимо ли это к type из Container.

* 1017.*

Конкретная форма очистки - это стратегия , которую вы используете для анализа конкретных данных.Ваш контейнер инкапсулирует эту стратегию, предоставляя интерфейс для нормализованных данных.

Вам просто нужно добавить некоторую логику / контракт между Container и Scraper, чтобы они моглиразговаривать друг с другом.Этот контракт вы можете заключить в интерфейс обоих.

Это также позволит вам иметь Scraper, который может работать с несколькими types, если вы хотите растянуть его.

Дляваш Container, взгляните также на SPL, чтобы реализовать некоторые интерфейсы, чтобы у вас были итераторы (и рекурсивные итераторы).Это может быть общая структура, на которую вы ссылаетесь, и SPL может повысить удобство использования ваших Container классов.

Вам не нужно жестко кодировать все в ООП, вы можете сохранять динамичность, особенно вPHP вы обычно разрешаете вещи во время выполнения.

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

Только мои 2 цента.

...