1) 这不安全吗?
不是天生的,不。
2) 嵌套对象内的 var 是否是调用所独有的
在 $tom->__construct() 中,或者如果我使用 new 创建另一个对象
objectBuilder() 是否覆盖了 $tom->someObject 中的那个?我
没有注意到这一点,但不确定如何完全测试。
这是类和对象之间的一个基本问题。对象是一个类的实例,可以有多个。唯一会被覆盖的是静态属性和方法。你可以这样测试它:
<?php
$obj1 = new objectBuilder();
$obj2 = new objectBuilder();
if ($obj1 !== $obj2) {
echo "objects are not the same\n";
}
if ($obj1->buriedVar !== $obj2->buriedVar) {
echo "nested objects are not the same either\n";
}
$obj3 = new objectBuilder(1);
if ($obj1->buriedVar != $obj3->buriedVar) {
echo "even the values of two different buried vars with different values are different.\n";
}
if ($obj1->buriedVar == $obj2->buriedVar) {
echo "counter-example: nested variables with the same values set are similar.\n";
}
这有助于了解平等和身份之间的区别 (see this SO post)。
3) 我还有什么遗漏的吗?最佳实践理由
在类中实例化一个对象?我已经用了很多年了
它非常适合我所做的事情。是速度吗?
你简单地谈到了它。您应该知道这是不可扩展且难以测试的。
假设您正在为狗创建一个网站。
<?php
class Bio
{
public function __construct()
{
$this->dog = new Dog('Terrier');
}
}
class Dog
{
private $animal = 'dog';
private $noise = 'woof!';
private $breed;
public function __construct($breed=null)
{
$this->setBreed($breed);
}
public function setBreed($breed)
{
$this->breed = $breed;
}
}
如果你想添加一个新品种怎么办?嗯...这很简单:
class Bio
{
// ...
public function __construct($breed)
{
$this->dog = new Dog($breed);
}
// ...
}
酷!你已经解决了一切。
除了...
有一天你想为猫创建一个栏目,因为你最好的作家之一也喜欢猫,而你感觉到了一个尚未开发的市场。
哦哦...
当然,您可以重构代码。但是你很久以前写的。现在你必须进去弄清楚一切都去了哪里。没什么大不了的..有点烦人,但你修好了!
但是现在你有另一个问题。事实证明,同一位作者想为该品种添加不同的特征。你很惊讶这没有早点出现,但是,嘿,这可能是件好事。
现在您需要进入 Dog 对象和 Cat 对象,并添加特征。
每一次。
开启。每一个。生物。
经过一些重新配置后,您已经创建了类似这样的怪物:
$article1 = new Bio('Terrier', 'dog', ['independent']);
$article2 = new Bio('Persian', 'cat', ['flat-faced']);
//... and so on, and so on
下次作者要求什么时,你解雇她,然后愤怒地扯掉你的头发。
或者,从一开始,您就使用依赖注入。
<?php
class Bio
{
private $animal;
public function __construct(AnimalInterface $animal)
{
$this->animal = $animal;
}
}
interface Animal
{
public function getType();
public function setBreed($breed);
public function getBreed();
public function setTraits(array $traits);
public function getTraits();
}
abstract class AbstractAnimal implements AnimalInterface
{
private $breed;
private $traits = [];
abstract public function getType();
public function setBreed($breed)
{
$this->breed = $breed;
}
public function getBreed()
{
return $this->breed;
}
public function setTraits(array $traits)
{
$this->traits = $traits;
}
public function getTraits()
{
return (array)$this->traits;
}
}
class Cat extends AbstractAnimal
{
public function getType()
{
return 'cat';
}
}
class Dog extends AbstractAnimal
{
public function getType()
{
return 'dog';
}
}
此模式在创建后几乎不需要编辑。
为什么?因为您是注入要嵌套到类中的对象,而不是在对象中实例化它。
$bio1 = new Bio($dog); $bio2 = new Bio($cat);可以一直这样。现在您只需编辑$dog 和$cat 对象。额外的好处是这些对象可以在任何地方使用。
但是实用程序类呢?
(这就是可测试性的用武之地。如果您还没有使用过单元测试,我建议您在下面的 PHPUnit 链接中阅读它。我不会详述它是如何工作的,因为它是题外话) .
如果您有需要自定义的类,那么依赖注入非常好。但是只包含各种功能的实用程序类呢?
class Utils
{
public function add($a, $b)
{
return $a + $b;
}
}
你可能认为你可以从构造函数中安全地调用这个函数。你可以。但是,有一天您可能会在您的 Utils 类中创建一个 log 方法:
public function log($msg)
{
exec("cat '$msg' > /tmp/log.txt");
}
这很好用。但是,当您运行测试时,您的 /tmp/log.txt 文件会报错。 "Invalid permissions!"。通过您的网站运行此方法时,log.txt 需要可被www-data 写入。
您可以只使用chmod 777 /tmp/log.txt,但这意味着有权访问您服务器的每个人都可以写入该日志。此外,您可能不希望在测试时总是写入与在 Web 界面中导航时相同的日志(我个人觉得这很混乱)。
PHPUnit 和其他单元测试服务允许您模拟各种对象。问题是你有类直接调用Utils。
您必须找到一种方法来手动覆盖构造函数。查看PHPUnit's manual 了解为什么这可能不理想。
如果你不使用依赖注入,你会怎么做?
PHPUnit 建议除其他修复外,将这个 Utils 对象实例化移动到另一个方法,然后在您的单元测试中存根/模拟该方法(我想强调这是 推荐依赖注入)。
那么下一个最好的?
public function __construct()
{
$this->init();
}
private function init()
{
$this->utils = new Utils;
}
现在当你进行单元测试时,你可以创建一个假的init 方法,它会在类创建后立即被调用。
总之,您当前实例化类的方式在许多现实世界的情况下是不可扩展的或容易测试的。虽然在有限的情况下可能没问题,但最好习惯 DI(依赖注入)模式,因为它会在未来为您省去很多麻烦。