许多人声称OOP中使用的全局函数是个坏主意,因为你无法测试(模拟)它们。我应该用什么呢?总是依赖注射?
我有一些工具箱,其中包含许多小功能,可以执行特定且有用的功能。例如。转换字符串,进行一些计算,清理凌乱的HTML等。在代码中使用它们非常方便。例如:
public function copyUploadedFile($filename, $dst_dir)
{
$filename = convertToSimpleString($filename);
if (!$filename) $error = displayError('Filename is empty!');
$extension = getFileExtension($filename);
$dst_dir = convertToSimpleString($dst_dir);
if (!isDir($dst_dir)) createDir($dst_dir);
....
}
依赖注入使代码更加复杂,并且即使对于非常小的工作也始终强制创建新对象:
$file = new FileManager(new ErrorHandler, new StringConverter, new DirManager ...)
public function __construct(ErrorHandler $error_handler, StringConverter $string_converter, DirManager $dir_manager, ...)
{
$this->error_handler = $error_handler;
$this->string_converter = $string_converter;
$this->dir_manager = $div_manager;
...
}
答案 0 :(得分:1)
依赖注入是控制反转的模式。依赖关系的控制从被调用的一个调用到一个调用。
主要优势:来电者链顶端的那个总是你。您可以控制所有依赖项,并完全控制应用程序的工作方式。您可以用另一个(例如您制作的)替换依赖项。
例如,如果库X使用Logger Y并且您想让它使用您的记录器Z,该怎么办?使用依赖注入,您不必更改库X的代码。
http://php-di.org/doc/understanding-di.html
每个错误应用的模式都会成为反模式。依赖注入用于注入" Injectables" (例如服务,存储库,数据库,模板引擎,记录器)从外部进入"核心"申请。 DI不适用于" Newables" Newables的例子是Value Objects,DTO&POCO&。这种对象可以(应该)在没有DI的情况下创建。
PHP支持OOP和程序样式的混合。像file_get_contents
这样的全局函数可以在任何地方使用。但问题(或问题)是:你想如何单元测试那个? 1.解决方案:使用vfsStream作为虚拟文件系统来模拟真实的文件系统。 2.解决方案:使用OOP接口(1,2)并注入模拟对象。
我认为DI的主要好处之一是:
为了管理新的复杂性,我建议使用像PHP-DI这样的工具。应在prod服务器上启用缓存以提高性能。一切都有它的价格; - )
答案 1 :(得分:0)
首先,您必须区分两种类型的函数:PHP的本机函数和用户定义的函数。
当您在任何类中使用PHP的内置函数时,唯一会影响可测试性(以及一般行为)的函数将是访问某些外部全局状态的函数。例如,time()
和php_sapi_name()
等函数依赖于应用程序外部的全局状态。最佳实践是将它们作为参数传递给对象的方法或构造函数(依赖注入)。
注意:如果不可能,那么替代方法是在类方法中换行并在运行单元测试时使用反射来改变它们的行为。
对于像array_key_exists()
或strpost()
这样的函数,这些函数实际操作现有的应用程序状态,并且始终具有可预测的结果。它们不会对代码的可测试性产生任何不利影响。
但是你实际上并没有谈论那些。
当涉及到用户定义的功能时,那些实际上是不可测试的。这意味着您要么必须将它们视为“受信任”(这是一个值得怀疑的想法),要么必须避免使用它们。
如果你打算实际使用面向对象的范例,我会建议避免使用它们,因为它们是程序范式的核心概念,实际上会导致你的OOP代码变得混乱。
另外,请观看此讲座:https://www.youtube.com/watch?v=KHBrDWIKW8Q
现在,实用的部分。
这一切都始于您使用的顶级API。正如我所看到的,在高级别,您的代码应该看起来像这样:
try {
$file = new File($filename);
$storage->move($file, '/path/to/destination');
} catch (EmptyFilename $exception) {
echo 'File name is missing!';
} catch (DirectoryNotWritable $exception) {
echo 'Yeah no, you cant write there. Get lost!';
}
“这个文件实际上是有效的上传”部分应该是文件抽象的责任。像$file->getExtension()
这样的代码也是如此,而“移动文件”应该是存储抽象的责任。
您的存储抽象不应该负责格式化正确的错误消息。相反,它应该抛出一个异常,并在调用堆栈中让更高的东西来处理渲染。