PHP面向对象编程中访问方法的良好实践?

3

我有一些代码经常看起来像这样:

private $user;

public function __construct()
{
    $this->user = User::getInstance(); //singleton
}

public function methodOne()
{
    return $this->user->foo();
}

public function methodTwo()
{
    return $this->user->foo2();
}

public function methodThree()
{
    return $this->user->foo3();
}

我想如果我将用户属性设置为实例,我可以在我的方法中重复使用一个较短的名称(虽然在这种情况下它并不是那么短)。 我也认为以这种方式做可能会节省一些资源(开始怀疑它),但当我查看其他人的代码时,我很少看到人们这样做。 他们通常只会调用:

User::getInstance()->foo();
User::getInstance()->foo2();
User::getInstance()->foo3();

有没有最佳实践可以参考?如果不是单例类,也许可以这样做?或者你永远不应该这样做?希望能得到一些澄清,谢谢。
编辑: 以防有任何误解,我只是想知道是否应该使用创建属性来存储实例的第一个示例,而不是这种方式:
public function methodOne()
{
    return User::getInstance()->foo();
}

public function methodTwo()
{
    return User::getInstance()->foo2();
}

public function methodThree()
{
    return User::getInstance()->foo3();
}

实际上,现在我想起来了,这可能需要的代码更少,因为我不需要构造函数...

4个回答

2

在PHP中,我个人偏好使用只有静态方法的类来实现单例模式,这样你就可以

User::foo();
User::bar();

可以最简单地使用__callStatic实现。 - mario

2

我不会仅仅为了包装一个单例而创建一个新类。但是,如果您的新类添加了一些额外的逻辑,则您的示例就有意义了。请记住,如果您担心自己太啰嗦,您可以始终为连续的函数调用使用临时变量。

$user = User::getInstance();
$user->foo();
$user->bar();

个人而言,我不再使用单例模式了,而是使用依赖注入。我喜欢 sfServiceContainer,但还有其他选择。可以看看这系列文章:http://fabien.potencier.org/article/11/what-is-dependency-injection 更新 根据附加评论,以下是我的做法:
class UserWrapper
{
    private $user = null;

    public function __construct($user)
    {
        $this->user = $user;
    }

    public function foo()
    {
         return $this->user->foo();
    }

    ...
}

然后像这样使用它:
$user = new UserWrapper(User::getInstance());

为什么?这样我就可以传递一个假的User对象来测试UserWrapper类。例如:

class UserMock { ... } // A fake object that looks like a User
$userTest = new UserWrapper(new UserMock());

是的,还会有一些额外的逻辑。这是与另一个应用程序集成的一种方式,因此我包装了User类方法,以后如果我更改应用程序,就不必重命名所有内容。对于你展示的代码,我确实这样做,但只是想知道在每个方法中仅使用实例一次时使用的最佳方法是什么。我已编辑我的主要帖子。 - Joker
如果您添加额外的逻辑,那么您的代码就没问题了。我唯一想改变的是不要在构造函数中获取User实例,而是将其作为参数传递给构造函数(以便于依赖注入)。 - Sander Marechal
抱歉,也许我还没有解释清楚,我只是想知道将变量的实例存储起来是否比直接调用实例更好,就像上面两个示例中所看到的那样。我倾向于第二种方式(每次直接调用它),因为它似乎更清晰明了。 - Joker
我会使用第一种方式。我已经在我的答案中更新了一个例子。 - Sander Marechal

2
你的方法确实存在一些问题:
  • 你的类是否依赖于User类并不清晰。通过将User作为构造函数参数添加可以解决这个问题。
  • 单例模式通常是一个不好的实践,你的代码也说明了这一点:它是全局可访问的,因此难以使用它来跟踪依赖项(这指向上述问题)。
  • 静态方法经常被用作全局访问点(响应人们通常会使用User::method()的情况)。全局访问点与单例模式存在相同的问题。它们也稍微更难测试。

我也不明白为什么要重复使用User对象作为新对象的一部分,除非你要使用适配器模式等。也许如果你能澄清这一点,我就能提出比“generic”更好的替代方案:

class Foo {
    public function __construct(User $user) {
        $this->user = $user;
    }
    public function doXsimplified() {
        $this->user->doXbutMoreComplex($arg1,$arg2, $arg20);
    }
}

我编辑了我的帖子,同时也创建了一个包装器,因为它要与另一个应用程序集成,所以如果我以后更改应用程序,就不必重命名所有的方法。我也不确定是否能够使用你展示的方法,因为我还没有用户对象。 - Joker
@Joker 这是一个很好的包装器使用案例。你的用户对象在包装器对象出现时就开始发挥作用了(因为它是用来操作第三方用户对象的)。所以,如果你在创建另一个对象之前或之后创建用户对象并没有什么区别。 - koen
是的,没有区别,我认为我还没有描述得足够清楚。实际上,它更加简单。我想知道是否应该将实例存储在由构造函数设置的属性中,还是直接获取实例(如上面的两个示例所示)。我认为这只是一种偏好?现在我倾向于第二种方式,因为它似乎使事情更清晰。 - Joker
@Joker 我会遵循“隔离变化”的规则。变化最有可能发生在哪里,如果发生了什么将为您节省最多的工作(无论差异多小)?但是,如果您发现第二个示例更清晰,那就支持它的观点。 - koen

0

通常我是这样做的,如果您已经在某种引导程序或配置文件中包含了类。我通常会在每个页面加载时调用的引导程序中声明$user变量,然后在其他php文件中将其作为全局变量引用,这是我在引导文件中所拥有的。

$user = new User();

那么这就是在调用php文件中我将会有的内容

global $user;
$user->foo();

我认为在PHP中使用全局变量不是“良好的面向对象编程实践”之一。 - Alex Bailey

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