静态函数很糟糕 - 但有什么替代方法呢?
在我的示例中,我使用的是 PHP 框架 Yii2,但我认为这适用于大多数 OO 语言.
In my example I'm using the PHP framework Yii2 but I think this applies to most OO languages.
我有一个 ActiveRecord
基类,我的大部分业务对象都从它扩展而来,例如项目
.
I have an ActiveRecord
base class which most of my business objects extend from e.g. Project
.
目前如果我想要一个 Project
实例,我会调用
At the moment if I want a Project
instance I call
Project::findOne(['id' => $id]);
findOne 是 ActiveRecord
(它是 Yii2 框架的一部分)的一个静态方法.所以这是不好的形式,因为在编写单元测试时我不能轻易地模拟/存根这个调用的返回.
findOne is a static method of ActiveRecord
(which is part of the Yii2 framework). So this is bad form because I can't easily mock/stub the return of this call when writing unit tests.
但是解决这个问题的最佳方法是什么?
But what's the best way to get around this?
我可以创建一个继承自 ActiveRecord
的类 CActiveRecord
并将静态调用包装在非静态调用中并在任何地方使用它 - 但随后我必须实例化一个丢弃的 Project
对象,以获取实际实例.如果 Project
对象需要一些繁重的配置来实例化怎么办 - 我会将随机的废话传递给构造函数只是为了获得一个实例.
I could create a class CActiveRecord
that inherits from ActiveRecord
and wrap the static call in a non-static call and use that everywhere - but then I would have to instantiate a throw-away Project
object in order to get the actual instance. What if the Project
object needed some heavy config to be instantiated - I would be passing random nonsense into the constructor just to get an instance.
总结:简单地将静态更改为非静态似乎是错误的 - 我不应该也将功能移到其他地方吗?如果有,在哪里?
推荐答案
静态调用的问题是与特定的其他代码段硬耦合.仅将其包装在动态"调用中并不能使这变得更好:
The issue with static calls is the hard coupling to a specific other piece of code. Just wrapping that in a "dynamic" call doesn't make this any better:
$c = new CProject;
$c->findOne(); // Calls Project::findOne()
这太没有意义了.问题不在于 ->
与 ::
的语法,问题在于此特定代码引用了特定的其他类,并且您无法轻松地将此类替换为别的东西.您正在类/对象之间构建严格的、硬编码的依赖项,这使得将它们分开、使您的代码难以测试以及使代码适应不同情况变得更加困难.
That's pretty darn pointless. The issue is not the syntax of ->
vs. ::
, the issue is that this particular code references a specific other class and that you cannot easily exchange this class for something else. You're building rigid, hardcoded dependencies between your classes/objects, which makes it hard to take them apart, which makes your code hard to test, and which makes it harder to adapt code to different situations.
替代方案是依赖注入:
function foo(Project $project) {
$p = $project->findOne();
}
此功能不与任何特定 Project
类耦合,而是与仅提供类似于Project
的接口的类耦合.事实上,Project
甚至可以只是一个interface
.在这里调用哪个特定的类和方法然后在完全不同的地方决定,比如你的依赖注入容器;或者只是此代码的调用者.
This function is not coupled to any one specific Project
class, but to a class which simply offers an interface akin to Project
. In fact, Project
could even be simply an interface
. Which specific class and method is getting called here then is decided somewhere completely different, like your dependency injection container; or simply the caller of this code.
这使得根据手头情况的需要,可以更容易地拆分此代码并以不同方式将其重新组合在一起.这并不是说它不能工作,并且您根本不应该使用静态调用,但是您确实需要了解您正在使用每个硬编码的类名建立哪些交叉依赖关系,以及这是否可能导致一个问题.对于即使是中等复杂度和/或增长的软件项目,它最终几乎肯定会导致某种形式的摩擦.
This makes it a lot easier to take this code apart and put it back together in different ways, as necessary for the situation at hand. That's not to say it can't work and that you should never use static calls at all, but you really need to be aware of what cross-dependencies you're establishing with every hardcoded class name, and whether that may or may not cause a problem down the line. For even moderately complex and/or growing software projects, it will almost certainly cause friction in some form or another eventually.
请参阅如何不使用静态破坏您的可测试性,了解更长的深度文章.
See How Not To Kill Your Testability Using Statics for a longer in-depth article.
相关文章