【问题标题】:Nested Objects in PHPPHP中的嵌套对象
【发布时间】:2017-12-29 03:38:03
【问题描述】:

可能已经有人问过这个问题了,但还是这样。这可能属于最佳实践或安全性...我不太确定。

在我的应用程序中,我使用了一个在__construct() 函数中调用的嵌套对象。有点像这样:

class user {
    public $userID = NULL;
    public $someObject = NULL;

    public function __construct() {
        $this->userID = getThisUser();
        $this->someObject = new objectBuilder($this->userID);
    }
    public function getThisUser() {
        // ... 
    }
}

class objectBuilder {
    public $buriedVar = NULL;

    public function __construct($uid = NULL) {
        if( !isset($uid) ) {
            $this->buriedVar = setTheObject($uid);
        } else {
            $this->buriedVar = setTheObject(0);
        }
    }
    public function setTheObject($id) {
        // ...
        return "random string";
    }
}

$tom = new user();

这里的轮廓显然很糟糕,但重点是,我可以调用$tom->someObject->buriedVar,它会返回"random string"

在寻找嵌套类的方法时,我注意到没有人推荐将其作为将对象存储在另一个对象中的方法。我很好奇一些事情:

1) 这不安全吗?
2) 嵌套对象中的变量是$tom->__construct() 内部调用所独有的,或者如果我使用new objectBuilder() 创建另一个对象,它会覆盖$tom->someObject 内部的那个吗?我没有注意到这一点,但不确定如何完全测试。
3)还有什么我想念的吗?不实例化类中的对象的最佳实践理由?我已经使用它多年了,它非常适合我所做的事情。是速度吗?

【问题讨论】:

  • “没有人推荐这个”
  • 通过将所有东西设为public,任何东西都可以将$tom->someObject->buriedVar 的值设置为其他值。您可能想查看关键字privatestatic。还有这个question and answer
  • @Phil 对不起,我不是说他们反对它,我的意思是他们根本没有推荐它。就像......他们没有把它作为解决人们询问的嵌套问题的建议。我只是好奇是否有理由不去回答作为无法解决问题的解决方法class something { class somethingElse { } }
  • @Tigger 是的。这就是我所说的可怕轮廓的意思,哈哈。感谢您指出这一点,以防其他人出现在这篇文章中。
  • 我应该指出......整个问题可能非常愚蠢。我已经到了尝试学习 PHP 的精髓的地步,这是 Google 很难问到的事情之一。可能没有答案,或者可能真的很明显。对不起,如果是这样。

标签: php


【解决方案1】:

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(依赖注入)模式,因为它会在未来为您省去很多麻烦。

【讨论】:

  • 非常感谢。这正是我要问的,但不知道如何问。我已经在 PHP 中达到了这一点,我不确定它与我所学的其他语言的细微差别,我只是不知道要问这样的事情。你已经看到并完美地回答了。谢谢!
猜你喜欢
  • 2015-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-12
  • 1970-01-01
  • 1970-01-01
  • 2013-03-26
  • 2014-11-24
相关资源
最近更新 更多