Инкапсуляция общей логики (проектирование, ориентированное на предметную область, лучшие практики)

StackOverflow https://stackoverflow.com/questions/515608

Вопрос

Обновленный: 02.09.2009 - Переработан вопрос, предоставлены лучшие примеры, добавлена награда.


Привет,
Я создаю PHP-приложение, используя шаблон сопоставления данных между базой данных и сущностями (объектами домена).Мой вопрос заключается в следующем:

Каков наилучший способ инкапсулировать часто выполняемую задачу?

Например, одной из распространенных задач является извлечение одной или нескольких объектов сайта из средства сопоставления сайтов и связанных с ними объектов (домашней) страницы из средства сопоставления страниц.В настоящее время я бы сделал это следующим образом:

$siteMapper = new Site_Mapper();
$site = $siteMapper->findByid(1);

$pageMapper = new Page_Mapper();
$site->addPage($pageMapper->findHome($site->getId()));

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

Мне нужно будет сделать это (получить сайт и связанную с ним домашнюю страницу, локаль и т.д.) В нескольких местах моего приложения, и я не могу придумать наилучший способ / место для инкапсуляции этой задачи, чтобы мне не приходилось повторять это повсюду.В идеале я бы хотел, чтобы в итоге получилось что-то вроде этого:

$someObject = new SomeClass();
$site = $someObject->someMethod(1); // or
$sites = $someObject->someOtherMethod();

Где результирующие объекты сайта уже имеют связанные с ними объекты, созданные и готовые к использованию.

Та же проблема возникает и при сохранении этих объектов обратно.Допустим, у меня есть объект сайта и связанный с ним объект домашней страницы, и они оба были изменены, я должен сделать что-то вроде этого:

$siteMapper->save($site);
$pageMapper->save($site->getHomePage());

Опять же, тривиально, но этот пример упрощен.Дублирование кода по-прежнему применяется.

На мой взгляд, имеет смысл иметь какой-то центральный объект, о котором можно было бы позаботиться:

  • Получение сайта (или сайтов) и всех необходимых связанных объектов
  • Создание новых объектов сайта с новыми связанными объектами
  • Создание сайта (или сайтов) и сохранение его и всех связанных объектов (если они изменились)

Итак, возвращаясь к моему вопросу, каким должен быть этот объект?

  • Существующий объект mapper?
  • Что-то, основанное на шаблоне репозитория?*
  • Что-то, основанное на единице работы паттена?*
  • Что-то еще?

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

Существует ли стандартный способ решения этой проблемы, и не мог бы кто-нибудь предоставить краткое описание того, как они это реализуют?Я не ищу никого, кто мог бы предоставить полностью работающую реализацию, только теорию.

Спасибо,
Джек

Это было полезно?

Решение

Используя шаблон репозитория / сервиса, ваши классы репозитория предоставляли бы простой интерфейс CRUD для каждой из ваших сущностей, тогда классы сервисов были бы дополнительным уровнем, который выполняет дополнительную логику, такую как прикрепление зависимостей сущностей.После этого остальная часть вашего приложения использует только эти Сервисы.Ваш пример может выглядеть примерно так:

$site = $siteService->getSiteById(1); // or
$sites = $siteService->getAllSites();

Тогда внутри класса SiteService у вас было бы что-то вроде этого:

function getSiteById($id) {
  $site = $siteRepository->getSiteById($id);
  foreach ($pageRepository->getPagesBySiteId($site->id) as $page)
  {
    $site->pages[] = $page;
  }
  return $site;
}

Я не очень хорошо знаю PHP, поэтому, пожалуйста, извините, если что-то не так синтаксически.

Другие советы

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

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

Файл:Site.php

class Site
{
   public static function Select($ID)
   {
      //Ensure current user has access to ID
      //Lookup and return data
   }

   public static function Insert($aData)
   {
      //Validate $aData
      //In the event of errors, raise a ValidationError($ErrorList)

      //Do whatever it is you are doing

      //Return new ID
   }

   public static function Update($ID, $aData)
   {
      //Validate $aData
      //In the event of errors, raise a ValidationError($ErrorList)

      //Update necessary fields
   }

Затем, чтобы вызвать его (из любого места), просто запустите:

$aData = Site::Select(123);

Site::Update(123, array('FirstName' => 'New First Name'));

$ID = Site::Insert(array(...))

Одна вещь, которую следует иметь в виду относительно OO-программирования и PHP...PHP не сохраняет "состояние" между запросами, поэтому создание экземпляра объекта только для того, чтобы его немедленно уничтожить, часто не имеет смысла.

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

Как бы вы назвали этот метод ?Название обычно намекает на то, к чему относится данный метод.

class Page {

  public $id, $title, $url;

  public function __construct($id=false) {
    $this->id = $id;
  }

  public function save() {
    // ...
  }

}

class Site {

  public $id = '';
  public $pages = array();

  function __construct($id) {
     $this->id = $id;
     foreach ($this->getPages() as $page_id) {
       $this->pages[] = new Page($page_id);
     }
  }

  private function getPages() {
    // ...
  }

  public function addPage($url) {
    $page = ($this->pages[] = new Page());
    $page->url = $url;
    return $page;
  }

  public function save() {
    foreach ($this->pages as $page) {
      $page->save();
    } 
    // ..
  }

}

$site = new Site($id);
$page = $site->addPage('/');
$page->title = 'Home';
$site->save();

Сделайте свой Сайт объектом Совокупный Корень чтобы инкапсулировать сложную ассоциацию и обеспечить согласованность.

Затем создайте Местоположение сайта который несет ответственность за извлечение совокупности сайта и заполнение его дочерних элементов (включая все Страницы).

Вам не понадобится отдельный PageRepository (при условии, что вы не создаете Page отдельным агрегированным корневым каталогом), и ваш SiteRepository также должен нести ответственность за извлечение объектов Страницы (в вашем случае с помощью ваших существующих картографов).

Итак:

$siteRepository = new SiteRepository($myDbConfig);
$site = $siteRepository->findById(1); // will have Page children attached

И тогда findById метод будет отвечать также за поиск всех дочерних страниц Сайта.Это будет иметь структуру, аналогичную ответу CodeMonkey1, однако я считаю, что вы получите больше пользы, используя шаблоны Aggregate и Repository, а не создавая конкретный сервис для этой задачи.Любой другой поиск / запрос / обновление агрегата сайта, включая любой из его дочерних объектов, будет выполняться через тот же SiteRepository.

Редактировать: Вот краткое руководство по DDD чтобы помочь вам с терминологией, хотя я бы действительно рекомендовал прочитать Эванс если вам нужна полная картина.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top