OOP最佳实践(特别是PHP)


OOP Best Practices (specifically PHP)

我已经用PHP开发了一段时间,但直到最近才转向OOP方法。

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

例如,假设我有两个对象,User和Listing

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

就我所见(如果没有,请告知),我有三种选择来实现这一点。

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

  2. 使所需的属性成为Listing的本地属性,并在初始化Listing时填充该属性(例如通过sql联接)

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

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

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

我意识到这可能没有"正确"的答案,但有人能就处理这种情况的最佳方法给出建议吗?

非常感谢Nick

我肯定更喜欢选项1。选项2没有遵循OOP背后的思想,因为用户信息不是列表的一部分,所以将其保存在一个单独的对象中。

记住以下规则:

  1. 一次加载不带关系的对象
  2. 如果您需要相关对象,请根据规则1在需要时加载它

许多ORM都是这样工作的,例如Doctrine(http://www.doctrine-project.org)。这叫做懒惰加载。

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

选项1是首选的OOP方式。

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

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

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

这样,您可以优化Listing对象的查询和内存使用,同时在需要时可以访问用户对象的所有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)的使用将不起作用

PS
这应该被视为优化破解。因此,只有当性能成为一个问题时,才能实现这一点;

在您匆忙选择选项#1之前,我建议您不要忘记,尝试将简单的对象模型应用于存储在数据库中的数据可能会带来很多复杂性。

只要读一下这篇文章,就可以知道我在说什么:"为什么面向对象数据库失败了"

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

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

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

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

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

我认为您需要了解数据传输对象的设计模式:

  • http://en.wikipedia.org/wiki/Data_transfer_object

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

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

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

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