ZF MVC - 对象和映射器


ZF MVC - Objects and Mappers

我正在将 ZF 用于 MVC 应用程序,并且对我的代码应该如何构建感到非常困惑。

我有一个程序应用程序,它基本上是 1 个巨大的长文件,其中包含我希望我的应用程序执行的所有功能......如:获取用户名($id)等。所以现在我正在用 ZF 重新制作整个东西,因为我当前的代码库不可行、废话且难以调试。

我是MVC的新手,并且对应该如何布局,应该与什么交谈等等感到非常困惑。所以我知道关于模板和控制器需要瘦的观点,你应该有胖模型,但我对逻辑需要在哪里感到困惑。

我正在做一个游戏,有常见的对象,比如......用户,村庄,军队,地图广场,资源等。

如果我完全从理论上思考它,我只会说:1 用户对象包含许多村庄,每个村庄属于一个正方形并包含一支军队(其中包含许多单位)。

所以我的想法是我的模型不应该包含任何逻辑,只是一个用于检索数据的获取和设置函数的列表,并且处理、提问的逻辑应该在映射器中完成......喜欢:

$villageMapper = new VillageMapper();
// Get village from database using mapper 
$village = $villageMapper->getVillage($id, new Village());

当我想确定两个村庄互相攻击的结果时,会在哪里做?我会做这样的事情吗:

$outcome = $villageMapper->determineAttackOutcome($village1, $village2);

或者我会说...一个里面有一点逻辑的战斗对象?

$battle = new Battle();
// Add participants
$battle->addAttacker($village1)->addDefender($village2);
$outcome = $battle->performAttack();
// Save village changes cause of battle
$villageMapper->save($battle->getAttacker());
$villageMapper->save($battle->getDefender());

我有一堆 DbTable php 文件,我想所有的数据库代码都存在于其中......所以我的问题是:我的 Mapper 对象真的应该只用于获取和保存到数据库之类的事情吗?

谢谢,多姆

MVC 有许多不同的解释,但我是这样理解的:

模型:几乎包含与特定项目相关的所有逻辑。每个必须建模的东西(在你的例子中是用户、绒毛等)都有一个模型来配合它。该模型具有获取数据和放入数据的功能(即getter和setter)。该模型还进行错误检查等,并确保没有输入任何冲突。

观点:没有任何逻辑。在 Web 应用程序中,这实际上只是说明在页面上放置内容的位置。在某些框架中,你可以为视图提供模型(即 ASP.NET MVC3),在其他框架中(如 php 的 Savant3),它可以被馈送任何东西。控制器通常为视图提供数据,但如果为视图提供模型,它只会从模型中读取,而不会写入模型。

控制器:控制用户和模型之间的交互。当用户执行某些操作时,控制器会将其转换为模型必须执行的操作。例如,如果你对程序说"请把我的角色向北移动6个空格",控制器会说"有什么东西可以跑到这里以北的6个空间吗?"如果它看到这个位置是清晰的,它会告诉角色模型"将自己向北移动6个空格"。完成此操作后,它将向视图发送有关应显示的任何内容的数据。控制器中实现的大多数实际逻辑应该是用户模型,而不是模型模型。模型之间的交互可以通过单个模型中的方法或其他封装某种行为或交互的模型来处理。

因此,关于您的实现:

我会制作一个战斗对象(这是一个模型),其构造函数需要两个村庄或任何正在战斗的东西。它将有一个称为 execute 或 doBattle 的方法,或者控制器会调用的方法,然后战斗对象将执行其逻辑来决定结果并更新战斗人员的状态(即降低生命值、提供经验等)。它会将结果返回给控制器,以便控制器知道该怎么做(即,如果控制器因为模型死亡而需要忘记模型,它会告诉它)。此返回值也可以是发送到视图以告知战斗结果的内容。

你的一些模型(如用户、村庄等)将保存在数据库中,因此模型将知道如何将自己映射到该数据库(或者它会与知道如何映射它的另一个层通信),并且还要注意更新数据库和东西的确切实现(控制器将调用实际方法来"保存", 但模型将是唯一知道幕后发生的事情的东西)。其他模型(如战斗)不需要存在于数据库中,因为它们只是封装某些交互的逻辑。

拥有一个胖模型意味着几乎所有的逻辑都存在于模型中。

一些问题...

如果您正在执行域驱动设计 (http://en.wikipedia.org/wiki/Domain-driven_design),您的村庄对象可以是管理该村庄的业务逻辑的聚合根。

战斗也可以是由两个(或多个)村庄对象组成的聚合根,或者接收两个村庄对象并返回"结果"对象的服务。你也可以做一些类似于$village->attack($anotherVillage)的事情,它可以返回一个战斗对象,然后你可以坚持下去。

我建议在创建和持久化这些业务对象时遵循领域驱动设计和存储库模式 http://msdn.microsoft.com/en-us/library/ff649690.aspx

Datamapper 应该仅用于存储和检索数据库中的数据,并将该数据映射到您的域对象(用户、村庄、军队、MapSquares)。

您可以将逻辑放在域对象中,但我喜欢使用服务层。

在控制器中,您将执行以下操作:

function  attackAction() {
    $gameService->doVillageBattle($villageId1,$villageId2);
}

游戏服务看起来像:

doVillageBattle($villageId1,$villageId2) {
    $village1 = villageService->getById( $villageId1);
    $village2 = villageService->getById( $villageId2);
    if ($village1->getStrength() > $village2->getStrength()) {
         $village1->winBattle();
         $village2->looseBattle();
         $villageService->save($village1);
         $villageService->save($village2);
    }
}

最后,乡村服务将有:

function save( $village ) {
    villageMapper->save( $village );
}

因此,控制器仅与服务通信,服务相互通信或与逻辑上关联的数据映射器通信。服务承载大多数"业务逻辑",并且独立于数据库。当然,数据映射器独立于服务和控制器。