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