【问题标题】:PHP OOP Good practice for accessing methods?PHP OOP 访问方法的好习惯?
【发布时间】:2011-03-12 23:29:15
【问题描述】:

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

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();
}

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

【问题讨论】:

    标签: php oop methods


    【解决方案1】:

    您的方法确实存在一些问题。

    • 尚不清楚您的类是否依赖于 User 类。您可以通过将 User 添加为构造函数参数来解决此问题。
    • 单例通常是不好的做法。您的代码说明了原因:它是全局可访问的,因此很难使用它来跟踪依赖关系(这指向了上述问题)。
    • 静态方法经常被用作全局访问点(响应您看到人们通常使用 User::method() 所做的事情)。全局接入点会出现与单例相同的问题。它们也更难测试。

    我也看不出用你的新对象重复用户对象的意义,除非你会使用适配器模式。也许如果您能澄清这一点,我将能够提出比通用更好的替代方案:

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

    【讨论】:

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

    我个人在 PHP 中的偏好是对单例使用只有静态方法的类,所以你有

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

    【讨论】:

    • __callStatic实现最简单
    【解决方案3】:

    我不会创建一个新类只是来包裹这样的单例。但是,如果您的新类添加了一些额外的逻辑,那么您的示例就有意义了。请记住,如果您担心自己过于冗长,您可以随时使用临时变量来进行连续的函数调用。

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

    但就个人而言,我不再使用单例了。相反,我使用依赖注入。我喜欢 sfServiceContainer,但还有其他的。看看这个系列文章:http://fabien.potencier.org/article/11/what-is-dependency-injection

    更新

    基于额外的 cmets,我会这样做:

    class UserWrapper
    {
        private $user = null;
    
        public function __construct($user)
        {
            $this->user = $user;
        }
    
        public function foo()
        {
             return $this->user->foo();
        }
    
        ...
    }
    

    然后像这样使用它:

    $user = new UserWrapper(User::getInstance());
    

    为什么?因此,如果我想测试 UserWrapper 类,我可以传入一个假的 User 对象。例如:

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

    【讨论】:

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

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

    $user = new User();
    

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

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

    【讨论】:

    • 我相信使用全局变量不是 php 中的“良好 oop 实践”之一
    猜你喜欢
    • 1970-01-01
    • 2012-11-25
    • 2014-08-11
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    • 2016-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多