PHP:用于管理实体类型的设计模式

时间:2016-08-08 11:25:06

标签: php design-patterns

我有User个实体。我想拥有这个实体的多个“类型”,具有不同的经理和存储库。所有类型的所有User个实体仅共享UserInterface。现在,我正在寻找一种组织一切的好方法。我想到的第一件事就是创造这样的东西:

interface UserTypeManagerInterface
{
  public function addUserType($name, RepositoryInterface $repository, ManagerInterface $manager);
  public function hasType($name);
  public function getRepository($type);
  public function getManager($type);
}

然后,在我想同时管理多种类型的User的地方,我会注入这个,并且在我想管理特定类型用户的地方,我只能注入特定的存储库和经理对象,其类型。

看起来像一个非常干净的方法,但与此同时,当我想使用UserTypeManager创建一个类的测试时,我需要模拟UserTypeManager,然后这个模拟需要返回其他模拟(存储库和管理器)。

这当然是可行的,但它让我想到是否可以避免这种情况。我能想到的唯一另一件事就是允许在测试过程中避免上述复杂性,这样就可以了:

interface UserTypeManagerInterface {
    public function addUserType($name, RepositoryInterface $repository, ManagerInterface $manager);
}

/**
 * My class managing multiple types of user.
 */
class ManageMultipleTypesOfUsers implements UserTypeManagerInterface {
    // ...
}

所以我只想将所有存储库和管理器添加到实现UserTypeManagerInterface接口的所有类中。所以对象会直接使用给他们的东西。

这样测试会更加清晰,因为我只需要模拟一个管理器和一个存储库来测试类ManageMultipleTypesOfUsers,但这感觉太过于过度工程了。 ;)

这里的中间地方是否可能?

1 个答案:

答案 0 :(得分:1)

我无法给出明确的答案,因为这是一种权衡。

据我所知,User是一个纯值对象?这是一个好的开始。这意味着操纵没有副作用是微不足道的。

现在,考虑一下这些影响你设计的变量:

  • 界面只是一种方法吗? [或者它的所有方法都是一种方法的琐碎包装器?]
  • 界面是否意味着让您的库用户定义自定义实现?
  • 实现只是添加断言(例如,接口需要方法delete(User $user) - 只允许管理员,其他实现只是抛出,基本权限检查)或使用截然不同的代码?
  • 我们有多少过度设计与不稳定的混乱?

那么,这些变量如何影响我们的决定?

  • 如果您只有一个[较小]的中心方法,那么接口和类可能会过度使用并且倾向于使用许多小文件丢弃您的代码库。经常通过Closure也可以达到同样的效果。
  • 除了简单的Closures / callables足够时,公共API应始终是接口。
  • 当实现只添加断言时,通常情况下,一个单独的类将请求分派给访问控制管理器[从数据库中读取/缓存权限]。
  • 过度设计与不稳定:50个文件,每3行不同代码最好合并为一个300行的文件。

由于我不知道要求是什么,我无法给你一个理想的解决方案。你提出的(第二)解决方案适用于过度设计的#34;情况下。

更为温和的解决方案是功能性方法:

function addAdminUser($name, RepositoryInterface $repository, ManagerInterface $manager) { /* ... */ }
function addNormalUser($name, RepositoryInterface $repository, ManagerInterface $manager) { /* ... */ }
// you even can pass this around as a callable ($cb = "addAdminUser"), or (pre-binding):
$cb = function($name) use ($repo, $mgr) { addAdminUser($name, $repo, $mgr); };

或从根本上说(如果普通用户是管理员用户添加的一个子集)[只要该功能本身是无副作用且其来电者不会太难以测试]:

function addUser($name, $type, RepositoryInterface $repository, ManagerInterface $manager) {
    /* ... common logic ... */
    if ($type == IS_ADMIN_USER) { /* ... */ }
}

如果这太激进了,也可以在addUser中注入一个回调:

function addUser($name, $cb, RepositoryInterface $repository, ManagerInterface $manager) {
    $user = new User;
    /* ... common logic ... */
    $cb($user, $repository, $manager);
}

是的,功能方法可能不是可测试的(即如果你直接调用它就不能模拟该函数(不通过可调用)),但它可能会带来可读性的巨大收益。保存普通间接的级别可能使您的代码更易于分析。我强调 may ,因为删除太多的间接可能会产生相反的效果。找到适用于您的应用程序的中间地带。

TL; DR :PHP为您提供了更具功能性的方法,但不太可测试。有时,这是一个很好的权衡,有时不是。这取决于具体的代码所在的中间位置。

P.s。:在你的第二个提案中,真正对我来说有点气味的唯一一件事就是在addUserType方法签名中有RepositoryInterface $repository, ManagerInterface $manager。你确定它不是构造函数的一部分吗?