【问题标题】:OOP Best Practices (specifically PHP)OOP 最佳实践(特别是 PHP)
【发布时间】:2011-06-01 07:43:40
【问题描述】:

我已经使用 PHP 进行了一段时间的开发,但直到最近才切换到 OOP 方法。

对我来说,一个不断出现的问题是 OOP 的东西“走多远”,特别是在执行速度和内存资源等方面。

例如,假设我有 2 个对象,用户和列表

列表始终链接到单个用户。 UserId 是 Listing 的一个属性,所以我知道它与哪个用户相关。 有时,在一个列表方法中,我需要访问相关用户的单个属性。

据我所知(如果没有,请告知)我有 3 个选项来完成此操作。

  1. 创建一个新的用户对象并通过 $user -> theProperty 访问相关属性

  2. 将所需属性设为列表的本地属性,并在初始化列表时填充此属性(例如,通过 sql 连接)

  3. 直接查询数据库,通过用户ID获取User所需的属性

在我看来,选项 1 和 2 更严格地遵守 OOP 规则,但由于初始化整个对象只是为了检索 1 个属性而导致性能下降。选项 3 占用的内存最少,但完全避开了 OOP。

此外,就在创建时填充对象而言,我的大多数对象在初始化后不久就通过一个“填充”方法填充了它们的大部分属性(因此只需要 1 个 DB 查询)。这通常被认为是最佳实践,还是更建议使用单独的方法来获取这些属性,并在需要时填充它们?

我知道对此可能没有“正确”的答案,但任何人都可以就处理这种情况的最佳方法提供建议吗?

非常感谢 尼克

【问题讨论】:

  • 您可以一举 (1+2) 在用户登录后立即获取有关用户的所有信息(假设登录),填充它并将其存储为对象,然后保留它到处获取有关他们的信息,直到他们注销然后销毁该对象。我想这就是我尝试的方式。
  • 除非您对这三种方法进行了剖析并发现其中一种会严重影响性能,否则您现在不应该担心内存消耗或处理速度。只使用有效的。
  • 如何让 User 对象成为 Listing 对象的属性,而不仅仅是 UserId?
  • 我会推荐方式 1,这样您就可以像(在列表方法中)$this->getUser()->getProperty() 一样访问它,但并不真正相关,但请查看 martinfowler.com/eaaCatalog/singleTableInheritance.html && martinfowler.com/eaaCatalog/concreteTableInheritance.html

标签: php oop


【解决方案1】:

我绝对更喜欢选项 1。选项 2 不遵循 OOP 背后的理念,因为用户信息不是您列表的一部分,因此请将其保存在单独的对象中。

记住以下规则:

  1. 立即加载一个没有关系的对象。
  2. 如果您需要相关对象,请根据规则 1 在需要时加载它

许多 ORM 以这种方式工作,例如 Doctrine (http://www.doctrine-project.org)。这称为延迟加载。

当你需要一个对象的一个​​属性时,你会发现自己加载了同一个对象的第二个属性更远的几行。在许多情况下,您会发现自己在一个完整的脚本中只针对一个对象执行大量数据库查询。

【讨论】:

  • 不过,请注意:仅当您不确定是否需要链接对象时才使用延迟加载。如果您已经知道您将需要它们,然后加载它们 - 当然不是全部,只是您知道您需要的那些 - 一次加载。对于报告(如打印报告),我主要使用直接查询或视图,因为它们中的大多数都需要汇总结果。
  • 感谢大家对 cme​​ts 的帮助 - 延迟加载对我来说是一个新概念,我将着眼于探索。我认为我在这里学到的要点是首先集中精力维护正确的 OO 实践,然后如果由于性能问题需要,稍后再应用其他技术,例如延迟加载。
  • 否决我的人能否给出一个理由。我愿意听听你的论点。
【解决方案2】:

选项 1 是首选的 OOP 方式。

或者,您可以在 php 中编写一个 包装对象,其中仅包含 user_id 和您需要的属性。

当调用方法或访问其他属性时,您可以加载并启动真实的用户对象。

这可以通过__get()、__set()和__call()方法来实现。

这样,您可以优化列表对象的查询和内存使用,同时在需要时访问用户对象的所有 OOP 优点。

代码看起来应该是这样的:

class LazyObject {

  private 
    $propertySubset = array(),
    $realObject;


 function __call($method, $args) {
    if ($this->realObject === null) {
      $this->initRealObject();
    }
    return call_user_func_array(array($this->realObject, $method), $args);
  }

  // __get, __set and initRealObject implementation goes here

}

注意事项

  • 您可以使用参考:myMethod(&$ref)$shortcut = &$object->prop 不起作用
  • 您需要手动检查是否为包装器填充了足够的属性。 没有足够的属性将导致查询分配,并且包装器只会减慢速度。
  • 除非您在lazyObject 中实现接口,否则无法使用接口(如ArrayAccess)

附言。
这应该被认为是优化黑客。因此,只有在性能成为问题时才执行此操作;

【讨论】:

    【解决方案3】:

    在您急于选择选项 1 之前,我建议您始终认为,尝试将简单的对象模型应用于存储在数据库中的数据可能会带来很多复杂性。

    请阅读本文以了解我在说什么: "Why did object oriented databases fail"

    你说尼克,没有“正确”的答案。但我们可以这样说:

    在 OOP 理想的良好实践中,$listing 对象应具有 $listing->user 属性,该属性是表示列表用户的对象。为了获得更好的性能,不应在实例化 $listing 后立即实例化此 User 对象。在 Java 传统编码中,您将有一个方法 ->getUser()。在 PHP 中,您可以在调用 ->user 属性时立即实例化该对象。

    但是当您只需要一个属性时检索完整的用户信息,这可能会花费很多无用的数据流。如果您的代码打算一次处理大量列表对象,则尤其如此。

    如果是这样,那么您必须稍微偏离理想的对象模型,以便更接近您的业务流程。然后只检索您需要的用户属性,这可能是一个很好的折衷方案。例如:$listing->getUserProp1()。如果您将来可能需要其他用户属性,则可以将方法更改为 $listing->getUserProprty('prop1')。

    但只有当您有时需要 User 属性时,此选项才有用。如果事实上您每次拥有一个列表对象时都需要它,那么最好让 User 属性已经用其他列表属性初始化。这是你的选项#2。这似乎是一个非常好的解决方案,因为您需要最小化数据流,并且如果您不需要列表对象附近的其他用户信息。

    【讨论】:

      【解决方案4】:

      我相信你需要看看数据传输对象设计模式:

      视图(页面)应该接收数据传输对象,这些对象不能与您的数据访问层使用的相同。

      通过一些参数化,您的业务层可能会返回 DTO,这些 DTO 具有由数据访问层返回的一组特定的一个或多个对象。

      例如,如果你想列出用户,也许你想知道他们的“组标识符”、“用户标识符”和“全名”。

      用户组的标识符将来自某个 Group 对象,而用户的标识符和全名将来自某个 User 对象,最后,UserDto 对象将具有 User 和 Group 对象的属性,这将是某些用户界面视图所需要的显示用户列表。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-04-14
        • 2011-03-29
        • 2013-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多