草庐IT

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

coder 2024-01-04 原文

我有 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 的地方,我会注入(inject)这个,在我想管理特定类型的用户的地方,我只能注入(inject)特定的存储库和管理器对象,为其类型。

似乎是一种非常干净的方法,但与此同时,当我想使用 UserTypeManager 为类创建测试时,我需要模拟 UserTypeManager,那么这个模拟将需要返回其他模拟(存储库和管理器)。

这当然是可行的,但它让我思考是否可以避免这种情况。我能想到的唯一另一件事是这样的,它可以在测试期间避免上述复杂性:

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

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

所以我只想将所有存储库和管理器添加到所有实现 UserTypeManagerInterface 接口(interface)的类中。因此对象将直接使用提供给它们的东西。

这种测试方式会更简洁,因为我只需要模拟一个管理器和一个存储库来测试类 ManageMultipleTypesOfUsers,但这感觉太像过度工程了。 ;)

这里甚至可能有中间立场吗?

最佳答案

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

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

现在,考虑这些会影响您的设计的变量:

  • 接口(interface)是否只是一种方法? [或者它的所有方法都是对一种方法的简单包装?]
  • 该接口(interface)是否旨在让您的库的用户定义自定义实现?
  • 实现只是添加断言(例如,接口(interface)需要方法 delete(User $user) - 只允许管理员,其他实现只是抛出,基本权限检查)还是使用截然不同的代码?
  • 我们有多少过度设计和无法测试的困惑局面?

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

  • 如果您只有一个 [较小的] 中央方法,那么接口(interface)加类可能会过度杀伤力,并且往往会在您的代码库中散布许多小文件。通常传递闭包也能达到同样的效果。
  • 除了简单的闭包/可调用文件就足够的情况外,公共(public) API 应始终是接口(interface)。
  • 当实现只添加断言时,通常最好使用一个类将请求分派(dispatch)给访问控制管理器[从数据库读取/缓存权限]。
  • 过度设计与不可测试:50 个文件每 3 行不同的代码最好合并到一个 300 行的文件中。

由于我不知道需求是什么,所以我无法给你一个理想的解决方案。您提出的(第二个)解决方案适用于“过度设计”的情况。

更温和的解决方案是函数式方法:

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 中注入(inject)一个回调:

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

是的,函数式方法可能不那么可测试(即,如果您直接调用函数(不作为可调用函数传递),您将无法模拟该函数),但它可能会给您带来可读性方面的巨大 yield 。节省琐碎间接级别可能使您的代码更易于分析。我强调的是可能,因为删除过多的间接访问可能会产生相反的效果。找到适用于您的应用程序的中间立场。

TL;DR:PHP 为您提供了一种更实用的方法,但可测试性较差。有时这是一个很好的权衡,有时则不然。这取决于中间地带所在的具体代码。

P.s.:在您的第二个提案中,唯一让我觉得真正有点味道的是在 addUserType 方法签名中包含 RepositoryInterface $repository, ManagerInterface $manager。您确定它不是构造函数的一部分吗?

关于PHP:管理实体类型的设计模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38828004/

有关PHP:管理实体类型的设计模式的更多相关文章

  1. ruby - i18n Assets 管理/翻译 UI - 2

    我正在使用i18n从头开始​​构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在ruby​​onrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  5. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  6. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  7. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  8. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  9. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  10. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

    我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

随机推荐