了解 PHP 对象类型比较


Understanding PHP Object Type Comparisons

继续学习Kent Beck的测试驱动开发示例,并用PHP重写示例。

第13章描述了一个测试,如果两个对象属于同一类型,则应返回true。在前面的章节中,评估是有效的,但是对于这个示例,我无法让它通过,而且我不确定为什么它会失败。

给定一个实现表达式接口的类"Sum":

    class Sum implements Expression {
    public $augend;
    public $addend;

    public function __construct($augend, $addend)
    {
        $this->augend = $augend;
        $this->addend = $addend;
    }
     // impl of Expression interface, but this smells to me, dupe implementation
    // also in Money
    public function plus($addend) {
        return new Sum($this, $addend);
    }
    public function reduce($to) {         
        $amount = $this->augend->amount + $this->addend->amount;
        return new Money($amount, $to);
    }
}

和表达:

    interface Expression {
    public function plus($addend);
    public function reduce($to);
}

我正在尝试在Bank对象(称为reduce)上调用一个方法,其第一个参数是Sum对象,该对象具有自己的reduce实现。但是,Java 示例将第一个 arg 指定为接口,而不是具体类:

    class Bank {
    // the book defines the $source param as type Expression, which is legal 
    // in Java but not in PHP
    public function reduce($source, $to) {
       return $source->reduce($to);
    }
}

最后,我的钱类:

    class Money implements Expression {
    public $amount;
    public $currency;
    public function __construct($amount, $currency) {
        $this->amount = $amount;
        $this->currency = $currency;
    }
    public function currency(){
        return $this->currency;
    }
    public function equals($compareObject) {
        return $this->amount == $compareObject->amount
         && $this->currency() == $compareObject->currency();
    }
    // static factory method that returns Dollar 
    // (reduces dependence on subclasses)
    static function dollar($amount) {
        return new Dollar($amount, "USD");
    }
    static function franc($amount) {
        return new Franc($amount, "CHF");
    }
    public function times($multiplier) {
        return new Money($this->amount * $multiplier, $this->currency);
    }

     // impl of Expression interface
    public function plus($addend) {
        return new Sum($this, $addend);
    }
    public function reduce($to) {
        return $this;
    }
}

运行此测试时:

$sum = new Sum(Money::dollar(3), Money::dollar(4));
$bank = new Bank();
$result = $bank->reduce($sum, "USD");
$this->assertEquals(Money::dollar(7), $result); //FAIL

断言失败,说明$result是 Money 类型而不是 Dollar,即使我已经验证了每个对象的属性匹配:

$this->assertEquals(Money::dollar(7)->amount, $result->amount);

$this->assertEquals(Money::dollar(7)->currency, $result->currency);

这是否是缺乏将对象本质上转换为特定类型的能力的影响?我没有更改子类的实现,之前的测试仍然通过:

$five = Money::dollar(5);
$this->assertEquals(new Money(10, "USD"), $five->times(2));   
$this->assertEquals(new Money(15, "USD"), $five->times(3));
$this->assertEquals(get_class($five), "Dollar");

两个不同类型的对象永远不能相等。PHP 不会让一个对象自己决定它是否等于另一个对象,就像 java 使用 equal-method。

http://php.net/manual/en/language.oop5.object-comparison.php

在你的测试用例中,你可以自己调用eqauls方法。

$this->assertsTrue( $result->equals(Money::dollar(7)) );

这与其说是使用 phpunit 的问题,不如说是一个架构问题。我给了你 2 个简单的解决方案,但你当然应该用一些设计模式来改进你的代码。

1.

如果必须将"USD"传递给构造函数,那么定义类Dollar有什么意义?请改用类Money

// static factory method that returns Dollar 
// (reduces dependence on subclasses)
static function dollar($amount) {
    return new Money($amount, "USD");
}
static function franc($amount) {
    return new Money($amount, "CHF");
}
public function times($multiplier) {
    return new Money($this->amount * $multiplier, $this->currency);
}

阿拉伯数字。

这不是

Sum of Dollar 返回可能使用另一种货币的Money的逻辑(如果您没有转换机制)。您可以像这样更改Sum的方法reduce

public function reduce() {         
    $money = clone $this->augend;
    $money->amount = $this->augend->amount + $this->addend->amount;
    return $money;
}

---

使用这些解决方案,您的测试将按预期工作:

$sum = new Sum(Money::dollar(3), Money::dollar(4));
$bank = new Bank();
$result = $bank->reduce($sum, "USD");
$this->assertEquals(Money::dollar(7), $result); // OK

因为此断言检查对象的类和属性的相等性。