静态方法 get - 这是不好的实践吗?

16

与同事讨论过这是否是不好的实践,现在我找不到在线上的即时例子。

我们有很多数据库对象映射器,并像以下方式调用它们的函数:

(示例) - setId方法从数据库获取行并将其设置为预定义属性

class Person {

    public static function get($id) {
        $object = new Person;
        $object->setId($id);
        return $object;
    }
}

使用它,我们可以像这样使用简单的结构:(其中我们从某个帖子中获得了id)

$person = Person::get($id);

取代

$person = new Person;
$person->setId($id);

现在,我的直觉告诉我这是一种不好的做法。但我无法解释为什么。也许这里有人可以解释这是为什么,或者不是不好的做法。

以下是一些我们使用它的其他示例。我们主要将其用于获取器(只有名称,没有代码)。几乎所有的获取器都执行一个查询,可能返回1个对象,然后使用结果的ID来使用setId方法。

class CatalogArticle {
   public static function get($id) { }
   public static function getByArticlenumber($articlenumber) {} //$articlenumber is unique in the database
   public static function getRandom() {} //Runs a query returning a random row
}

(相关)https://dev59.com/mG435IYBdhLWcg3w3EIi#5166527 - Gordon
只是我的个人看法,但 Laravel 的 Taylor Otwell 因为使用工厂模式的静态方法和其他静态方法而受到了批评,但是猜猜他们有100%的单元测试覆盖率。 - Michael J. Calkins
5个回答

15

这并不是什么可怕的事情。它是工厂方法设计模式的实现。原则上讲,这并不是什么坏事。

然而,在你的具体示例中,它并没有做任何重要的事情,所以我不确定它是否必要。你可以通过为构造函数添加一个(可能是可选的)参数来消除需要使用显式工厂的需求。然后,任何人都可以调用$foo = new Person($id);而不需要显式工厂。

但是,如果实例化很复杂,或者您想要构建几种只能通过逻辑确定的不同人类型,则工厂方法可能更好。例如,假设您需要通过某个参数确定要实例化的人的类型。那么,在Person上使用工厂方法是合适的。该方法将确定要加载的“类型”,然后实例化该类。

总的来说,静态方法很难测试,并且不像实例那样允许多态性变化。它们还在代码中创建了硬依赖关系。它们并不是可怕的,但如果您想使用它们,您应该认真考虑。一种选择是使用Builder抽象工厂。这样,您创建一个生成器/工厂的实例,然后让该实例确定如何实例化生成的类...

还有一点需要注意。我建议将该方法从Person::get()重命名为更具语义的名称,例如Person::getInstance()或其他适当的名称。


5

这篇博客文章将告诉你为什么人们不喜欢静态方法:

http://kore-nordmann.de/blog/0103_static_considered_harmful.html

关于你当前的代码片段,我最关心的问题是:一个人是否允许没有ID?如果它代表一个真实的人,我觉得它应该是构造函数参数。如果你使用这个类来创建新的人,那当然可能行不通。


这两个调用之间的区别很小。都“create”了一个Person类并设置了Id,所以在“硬编码依赖”方面你没有输赢。优势只有在你想能够将一个Person传递到另一个对象中,而那个对象需要更改ID时才会显示出来(例如,这篇博客文章应该比我在这里解释得更好)。


2
我只是在edorian的帖子中添加内容,但我过去曾经使用过静态get方法,在这种情况下,有一个缓存引擎,并且(例如)我可能会在memcache中拥有给定的Person对象,而宁愿从缓存中检索它,而不是去数据库。例如:
class Person {

    public static function get($id) {

        if(Cache::contains("Person", $id))
        {
           return Cache::get("Person", $id);
        }
        else
        {           
            //fictional get_person_from_database, basically
            //getting an instance of Person from a database
            $object = get_person_from_database($id);
        }
        return $object;
    }

}

通过这种方式,所有缓存处理都由相关类完成,而不是调用者担心缓存问题。


1

0

时间会改变事物。

如果您在测试方面遇到问题,可以使用AspectMock库https://github.com/Codeception/AspectMock

无论如何,静态并不是那么糟糕。要使用静态,您只需要知道自己在做什么以及为什么这样做。如果您只将静态放置为快速解决方案,则在99%的情况下这是一个坏主意。即使在1%的时间里,它仍然是一个糟糕的解决方案,但在需要时它可以节省时间。


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