Liskov替换原则和使用继承类的正确方法

Guy*_*kes 7 php architecture oop inheritance liskov-substitution-principle

我有一些处理程序("控制器")类,他们可以以某种方式处理项目:

interface IHandler
{
    public function execute(Item $item);
}

class FirstHandler implements IHandler
{
    public function execute(Item $item) { echo $item->getTitle(); }
}

class SecondHandler implements IHandler
{
    public function execute(Item $item) { echo $item->getId() . $item->getTitle(); }
}

class Item
{
    public function getId() { return rand(); }
    public function getTitle() { return 'title at ' . time(); }
}
Run Code Online (Sandbox Code Playgroud)

但是我需要在子Item类中添加一些新功能:

class NewItem extends Item
{
    public function getAuthor() { return 'author ' . rand(); }
}
Run Code Online (Sandbox Code Playgroud)

并在SecondHandler中使用它

class SecondHandler implements IHandler
{
    public function execute(Item $item) { printf('%d %s, author %s', $item->getId(), $item->getTitle(), $item->getAuthor()); }
}
Run Code Online (Sandbox Code Playgroud)

但是Item班级实际上并没有getAuthor方法.并且,如果我尝试在SecondHandler类中更改accept方法的签名,我将捕获E_STRICT有关声明兼容性的错误.当然,这是一种LSP违规.

我该如何解决这个问题?例如,我是否需要两个接口,INewHandler并且IHandler具有不同的execute方法签名?但它是某种代码重复.

此外,我不能使用__constructor(Item $item)__construct(NewItem $item) 处理程序(以及execute没有参数的方法),这将被视为更好的解决方案:它们必须是不可变的,并且在应用程序生命周期中只允许每个策略的单个实例.

Dan*_*ndo 2

正如您自己发现的那样,PHP 的类型提示实现有很多限制,这些限制使得场景(如您所描述的场景)变得比应有的困难。在 Java 和 Swift 等其他类型语言中,您的实现绝对是合法的。

\n\n

经过对你的问题的一些思考后,我找到了F\xc3\xa9lix提出的解决方案,但我认为与问题相比,它的设计过度了。

\n\n

我对你的问题的回答不是解决方案,而是我在使用 PHP 开发多年后给你的建议:

\n\n

放弃 PHP 中的类型提示并以动态方式进行开发

\n\n

与 Java/C++ 相比,PHP 更类似于 Ruby/Python/JavaScript,并且尝试从静态类型语言进行一对一复制会导致强制且复杂的实现。

\n\n

实现问题的解决方案很简单,因此不要使其过于复杂,并保持应有的简单性(KISS 原则)。

\n\n

声明不带类型的方法参数,并在真正需要的地方实施检查(例如引发异常)。

\n\n
interface IStrategy\n{\n    public function execute($item);\n}\n\nclass FirstStrategy implements IStrategy\n{\n    public function execute($item) {\n        echo $item->getTitle();\n    }\n}\n\nclass SecondStrategy implements IStrategy\n{\n    public function execute($item) {\n        // execute(NewItem $item) is identical to this check.\n        if (! $item instanceof NewItem) {\n            throw new Exception(\'$item must be an instance of NewItem\');\n        }\n        echo $item->getAuthor();\n    }\n}\n\nclass Item\n{\n    public function getId() { return rand(); }\n    public function getTitle() { return \'title at \' . time(); }\n}\n\nclass NewItem extends Item\n{\n    public function getAuthor() { return \'author \' . rand(); }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

再次强调,不要用 Java 来思考,而是尽可能遵循鸭子类型的方式。

\n\n

如果可能,尝试不要严格强制参数的类型,而是根据可用接口(鸭子类型)调整代码的行为。

\n\n
class SecondStrategy implements IStrategy\n{\n    public function execute($item) {\n        $message = $item->getTitle();\n\n        // PHP 5 interface availability check.\n        if (is_callable([$item, \'getAuthor\'])) {\n            $message .= \' \' . $item->getAuthor();\n        }\n\n        // With PHP 7 is even better.\n        // try {\n        //     $message .= \' \' . $item->getAuthor();\n        // } catch (Error $e) {}\n\n        echo $message;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我希望对你有帮助。^_^

\n