封装通用逻辑(域驱动设计,最佳实践)

时间:2009-02-05 12:00:00

标签: oop domain-driven-design theory encapsulation

更新时间: 09/02/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对象?
  • 基于存储库模式的东西?*
  • 基于工作单位的东西?*
  • 别的什么?

*我不会完全了解其中任何一个,你可能猜到了。

是否有一种标准方法可以解决这个问题,是否有人可以简要说明他们如何实施这个问题?我不是在寻找任何人提供一个完全可行的实现,只是理论。

谢谢,
千斤顶

5 个答案:

答案 0 :(得分:2)

使用存储库/服务模式,您的存储库类将为您的每个实体提供简单的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,所以请原谅语法错误。

答案 1 :(得分:1)

[编辑:此条目试图解决这样一个事实,即编写自定义代码以直接处理情况通常比尝试将问题纳入模式更容易。] < / p>

模式在概念上很好,但它们并不总是“映射”。经过多年的高端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不会在请求之间保持“状态”,所以创建一个对象实例只是为了让它立即被销毁通常没有意义。

答案 2 :(得分:0)

我可能首先将常见任务解压缩到某个地方的辅助方法,然后等待查看设计要求的内容。感觉现在还为时过早。

这个方法你叫什么名字?该名称通常暗示该方法所属的位置。

答案 3 :(得分:0)

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();

答案 4 :(得分:0)

使您的站点对象成为Aggregate Root以封装复杂关联并确保一致性。

然后创建一个SiteRepository,负责检索网站聚合并填充其子项(包括所有页面)。

您不需要单独的PageRepository(假设您不将Page设为单独的Aggregate Root),并且您的SiteRepository也应负责检索Page对象(在您的情况下使用现有的Mapper)。

所以:

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

然后findById方法将负责查找网站的所有网页子项。这将与CodeMonkey1给出的答案类似,但我相信您将通过使用Aggregate和Repository模式获益更多,而不是为此任务创建特定的服务。 Site聚合的任何其他检索/查询/更新,包括其任何子对象,都将通过同一SiteRepository完成。

修改:Here's a short DDD Guide以帮助您使用术语,但如果您想了解整个图片,我建议您阅读Evans