面向对象编程最佳实践(特别是PHP)

15

我已经使用PHP开发了一段时间,但最近才转向面向对象编程的方法。

对我来说,经常出现的一个问题是:OOP应该走多远,特别是在执行速度和内存资源等方面。

例如,假设我有两个对象,用户(User)和列表(Listing)。

列表始终与单个用户关联。UserId是Listing的属性,因此我知道它所涉及的用户。

在Listing方法中,偶尔需要访问相关用户的单个属性。

就我所看到的(如果不是,请告诉我),我有三种选择来完成这个任务:

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

  2. 将所需属性作为Listing的本地属性,并在初始化Listing时填充它(例如,通过SQL连接)。

  3. 直接查询数据库,通过用户ID检索所需的用户属性。

在我看来,选项1和2更严格地遵循OOP规则,但由于初始化整个对象只是为了检索1个属性,因此会影响性能。选项3将是最少内存的选项,但却完全绕过了OOP。

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

我知道可能没有“正确”的答案,但是否有人可以给出关于如何处理这种情况的最佳建议?

非常感谢 Nick


你可以一次性获取用户的所有信息(假设已经登录),将其填充并存储为对象,然后只需保留该对象以获取有关其的信息,直到他们注销,然后销毁该对象。我认为这就是我处理它的方式。 - robx
2
除非你对这三种方法进行了分析比较并发现其中一种显著地影响性能,否则现在不必担心内存消耗或处理速度。只需使用有效的方法即可。 - Gordon
将User对象作为Listing对象的属性,而不仅仅是UserId,这个想法怎么样? - brabec
我会推荐方法1,这样你就可以像(在列表方法中)$this->getUser()->getProperty() 这样访问它。虽然与此无关,但你可以看一下这个网址:http://martinfowler.com/eaaCatalog/singleTableInheritance.html && http://martinfowler.com/eaaCatalog/concreteTableInheritance.html - Hannes
4个回答

2
我明确地更喜欢选项1。选项2不遵循面向对象编程(OOP)的思想,因为用户信息不是您的清单的一部分,所以请将其保留在一个单独的对象中。
请记住以下规则:
  1. 一次性加载一个对象及其关系。
  2. 如果需要相关对象,请根据规则1在需要时加载它。
许多ORM都是这样工作的,例如Doctrine (http://www.doctrine-project.org)。这被称为延迟加载。
当您需要一个对象的一个属性时,您会发现自己在几行后加载该对象的第二个属性。在许多情况下,您会发现自己在一个完整的脚本中为了一个对象而执行大量数据库查询。

不过需要注意的是:只有在你不确定是否需要链接对象时才使用延迟加载。如果你已经知道你需要它们,那么就加载它们 - 当然不是全部,只加载你知道需要的部分。对于报告(如打印报告),我大多数情况下使用直接查询或视图,因为它们大多需要聚合结果。 - wimvds
感谢大家的有益评论 - 惰性加载是一个新概念,我将研究探索。我认为我在这里学到的主要观点是首先集中于保持适当的面向对象编程实践,然后如果由于性能问题需要,再应用其他技术,如惰性加载。 - Nick
能否给我点踩的人提供一个理由呢?我愿意听听你的论据。 - Rene Terstegen

1

选项1是首选的面向对象编程方式。

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

当调用方法或访问其他属性时,您可以加载和初始化真实的用户对象。

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

这样,您可以优化Listing对象的查询和内存使用,同时在需要时访问所有来自用户对象的面向对象编程好处。

代码应该类似于:

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)将不起作用。

附注:
这应该被视为一种优化技巧。所以只有当性能成为问题时才实现它。


1
在你急于选择方案1之前,我建议你要记住,尝试将简单的对象模型应用于存储在数据库中的数据可能会带来很多复杂性。
只需阅读一下,以了解我所说的是什么意思: "为什么面向对象数据库失败了" 正如你所说的,Nick,并没有一个“正确”的答案。但我们可以这样说:
在面向对象编程的理想实践中,$listing对象应该有一个$listing->user属性,它是表示Listing的用户的对象。为了提高性能,这个User对象不应该在$listing被实例化时立即实例化。在传统的Java编码中,你会有一个方法->getUser()。在PHP中,当你调用->user属性时,你可以立即实例化这个对象。
但是,当你只需要一个属性时,检索完整的用户信息可能会造成很多无用的数据流动。如果你的代码打算同时处理大量的Listing对象,这一点尤其重要。
如果是这样,那么您必须从理想的对象模型中稍微移开一点,以更接近您的业务流程。然后只检索您需要的用户属性,这可能是一个很好的折衷方案。例如:$listing->getUserProp1()。如果您将来可能需要其他用户属性,则可以将该方法更改为$listing->getUserProprty('prop1')。
但是,仅当您有时需要用户属性时,此选项才是好的。如果实际上每次您都有一个Listing对象时都需要它,那么最好已经使用其他Listing属性初始化了User属性。这是您的第二个选择。如果您需要最小化数据流并且不需要在Listing对象附近使用其他用户信息,那么这似乎是一个非常好的解决方案。

0

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

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

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

例如,如果您想列出用户,可能想要知道他们的“组标识符”,“用户标识符”和“全名”。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接