代码是否符合 Liskov 的替换原则


Does code comply with Liskov's substitution principle?

在升级之前,我正在 PHP5.4 上测试我的现有代码。我发现下面的代码不再有效,因为 PHP 已经收紧了它的继承模型。由于这种紧缩,我一直在阅读有关 SOLID 的信息,特别是 Liskov 的替换原则(我是一个自学成才的程序员(,这样我就可以改进我的代码,而不会遭受未来的"收紧"。

interface IComparable {
    public function equals(self $other);
}
class A implements IComparable{
    protected $var;
    public function __construct($v){
        $this->var=$v;
    }
    public function equals(self $other){
        return ($this->var == $other->var) ? 'equal' : 'different';
    }
}
$a1= new A(7);
$a2= new A(5);
$a3= new A(5);
echo $a1->equals($a2),"'n";
echo $a2->equals($a3),"'n";

PHP 5.3 结果:

  • 不同
  • 平等

PHP 5.4 结果:

PHP 致命错误:A::equals(( 的声明必须与 IComparable::equals(IComparable $other(

如果我这样编写代码,我可以避免 php5.4 错误:

interface IComparable {
    public function equals($other);
}
class A implements IComparable{
    protected $var;
    public function __construct($v){
        $this->var=$v;
    }
    public function equals($other){
        if(get_class($other) != get_class($this)) return false;
        return ($this->var == $other->var) ? 'equal' : 'different';
    }
}

但是修复是否符合 Liskov 的替换原则,因为该函数显然不会接受任何参数类型?如果没有,我如何编写一个可继承的函数来执行我需要的操作 - 比较相同类型的 2 个对象 - 并符合良好的 OOD 原则?

首先:PHP 5.3 行为是一个错误,所以你不能用它作为判断任何其他方法的标准。

展望未来,您的 5.3 版本代码中已经违反了 LSP。考虑:

interface IComparable {
    public function equals(self $other);
}

这说明"任何IComparable都可以将自己与任何其他IComparable进行比较"(比较的语义对于讨论并不重要(。然后,class A继续违反 LSP,因为它不支持与任何IComparable进行比较 - 仅与碰巧是A实例的那些进行比较。

PHP 5.4 版本没有违反 LSP,因为新版本的 IComparable 说"我可以将自己与任何其他对象进行比较",这正是class A所做的。

如果您打算保留 5.3 版本合同,那么IComparable应该阅读

interface IComparable {
    public function equals(IComparable $other);
}

class A当然会使用相同的签名。这不会违反 LSP,并且可以在两个版本中正常工作。

如果你的意图是声明"一个IComparable实例可以将自身与相同类型的实例进行比较,不管是什么",那么你就不走运了,因为这个协定不能使用方法签名和类型提示来表达。

更新:原来你的意图是声明"这个类的实例可以比较自己",IComparable只是为了迫使你不要忘记这样做。

在这种情况下,解决方案就是简单地忘记IComparable并在A::compare()的签名中使用self。您确实失去了编译器强制您记住定义必要方法的问题,但恕我直言,这是一个小问题(特别是因为接口只声明一种方法(。

我相信你的界面和类需要写成如下才能更健壮:

interface IComparable
{
    /** 
     *  @return boolean 
     */
    public function equals(IComparable $other);
    /**
     *  @return mixed
     */
    public function getValue();
}

class A implements IComparable{
    protected $var;
    public function __construct($v){
        $this->var=$v;
    }
    /**
     *  @return mixed
     */
    public function getValue()
    {
       return $this->var;
    }
    /** 
     *  @param  IComparable $other
     *  @return boolean 
     */
    public function equals(IComparable $other)
    {
        return ( $this->getValue() === $other->getValue() );
    }
}

php 中的 self 关键字是指当前类,我建议不要将其用于类型提示参数,因为它不太清楚方法参数是什么。

关于你实现

平等和李斯科夫替换,这是一个你提到的有趣的主题。我会在这里看到关于平等和利斯科夫的有趣讨论。