我回答了一个问题(link),我在另一个班级中使用了新对象的创建。构造函数,这里是示例:
class Person {
public $mother_language;
function __construct(){ // just to initialize $mother_language
$this->mother_language = new Language('English');
}
我收到了用户" Matija"的评论。 (his profile)并且他写道:你永远不应该在对象consturctor中实例化一个新对象,应该从外部推送依赖项,所以使用这个类的任何人都知道这个类依赖于什么!
一般来说,我同意这一点,我理解他的观点。
但是,我过去经常这样做,例如:
ArrayAccess
接口的类)),这个类将在另一个中使用class,有这样的对象列表,DateTime
个对象,include
(或自动加载)依赖类,则应该没有错误问题,因为依赖对象的数量非常大,将它们全部传递给类构造函数可能会很长而且不清楚,例如
$color = new TColor('red'); // do we really need these lines?
$vin_number = new TVinNumber('xxx');
$production_date = new TDate(...);
...
$my_car = new TCar($color, $vin_number, $production_date, ...............);
因为我"出生"在Pascal,然后在Delphi,我有一些习惯。在Delphi(以及FreePascal作为其竞争对手)中,这种做法经常发生。例如,有一个TStrings
类处理字符串数组,并且存储它们不会使用array
,而是另一个类TList
,它提供了一些有用的方法,而{ {1}}只是某种界面。 TStrings
对象是私有声明的,并且无法从外部访问,但TList
的getter和setter。
请解释一下,避免在构造函数中创建对象真的很重要吗?
我已经阅读了this discussion,但仍然不清楚。
答案 0 :(得分:12)
是的,确实如此。然后,您可以清楚地了解对象需要什么才能构建。传入的大量依赖对象是一种代码气味,可能是你的类做得太多,应该在多个较小的类中分解。
如果要测试代码,则传递依赖对象的主要优点是。在您的示例中,我不能使用假的语言类。我必须使用实际的类来测试Person。我现在无法控制语言的行为以确保Person正常工作。
这篇文章有助于解释为什么这是一件坏事以及它造成的潜在问题。 http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/
的更新强> 的
除了测试之外,传入依赖对象还可以使您的代码更加明确,灵活和可扩展。引用我链接到的博客文章:
当协作者构造与初始化混合在一起时,它表明只有一种方法可以配置该类,从而关闭原本可用的重用机会。
在您的示例中,您只能创建将“英语”作为语言的人。但是当你想要创造一个说“法语”的人时呢?我无法定义。
至于创建对象并传入它们,这就是Factory
模式http://www.oodesign.com/factory-pattern.html的全部目的。它会创建依赖项并为您注入它们。因此,您可以向它询问将以您想要的方式初始化的对象。 Person对象不应该决定它需要什么。
答案 1 :(得分:7)
原来的评论对我来说似乎很愚蠢。
没有理由害怕管理资源的类。实际上,类提供的封装对于此任务来说是 perfecty apt 。
如果你限制自己只是从外部推动资源,那么应该管理这些资源?意大利面程序代码?糟糕!
尽量避免相信你在互联网上阅读的所有内容。
答案 2 :(得分:4)
这是一个可测试性问题,而不是正确/错误的事情,如果你在构造函数上创建一个对象,你就永远无法独立测试你的类。
如果你的类需要在构造函数上进行大量注射,你总是可以使用工厂方法来注入这些对象。
通过这种方式,您可以自由地模拟任何注入的对象来真正测试自己的类。
<?php
class Person {
public $mother_language;
// We ask for a known interface we don't mind the implementation
function __construct(Language_Interface $mother_language)
{
$this->mother_language = $mother_language
}
static function factory()
{
return new Person(new Language('English'));
}
}
$person = Person::factory();
$person = new Person(new Language('Spanish'); // Here you are free to inject your mocked object implementing the Language_Interface
答案 3 :(得分:3)
我认为这取决于具体情况。例如,如果您查看Doctrine 2,建议您将值始终设为null并在构造函数中设置值。这是因为Doctrine 2在从数据库中检索时会跳过实例化的类。
在那里实例化一个新的DateTime或ArrayCollection是完全可以接受的,你甚至可以添加默认的关系对象。我个人喜欢注入你的依赖项,保持你的代码很容易测试和修改,只需要很少的努力,但有些事情在构造函数中实例化更有意义。
答案 4 :(得分:2)
这些答案中的大多数似乎都是合理的。但我总是做你做的事。如果我正在测试它,那么我要么有一个setter或者只是设置它(看到你“mother_language”是公开的)。
但我个人认为这取决于你在做什么。从我看到的其他人发布的代码中,如果你的类实例化对象,构造函数应该总是采用一些参数。
如果我想创建一个实例化另一个对象的对象,该内部对象实例化另一个对象,等等,该怎么办?这似乎让我手上有很多东西。
对我来说,这句话似乎在说,“不要使用camelCase,使用under_score”。我想你应该做任何适合你的方式。毕竟,你有自己的测试方法,不是吗?
答案 5 :(得分:2)
如果你想设置默认值,但提高灵活性/可测试性,为什么不这样做呢?
class Person
{
protected $mother_language;
function __construct(Language $language = null)
{
$this->mother_language = $language ?: new Language('English');
}
}
有些人可能不喜欢它,但 是一种改进。
答案 6 :(得分:0)
在构造函数内部构造新对象没有错。两种方法都有优点。通过构造函数参数获取所有构造函数依赖确实使您的类更加灵活,尤其是在为您的类编写自动化测试时,这种灵活性特别有用。但这还会创建一个间接层。这也使该类在一般用例中使用起来不太方便,因为要考虑的构造函数参数更多。这也使得很难确切地知道该类的行为,因为您不确切知道将要传递的内容。这使该类变得不那么简单。
通过参数获取所有构造函数的依赖关系是使用一种称为“控制反转”的方法。我同意,熟悉这项技术并能够使用它很重要,但是从根本上讲,提升是完全相反的。控制权的颠倒是邪恶的,但有时却是必然的邪恶。有人会说这总是必要的。我不同意这一点,但是即使我同意,我也要说这始终是必不可少的邪恶。控制反转使您的代码以上述方式不那么直接。当人们主张控制反转时,他们真正的意思是“使用代码是不好的”。我并不是说人们故意在说这话,因为如果这样做的话,他们可能不会推广它。我说的是这些概念用相同的方式表达不同的意思,并希望通过意识到他们在说这些,一些人会重新考虑它。我在我的文章"Using code is bad"中概述了提出这一要求的原因。