【问题标题】:Abstract class or interface where declaration method signature allows type hinting of child classes声明方法签名允许子类类型提示的抽象类或接口
【发布时间】:2019-08-03 17:11:13
【问题描述】:

TLDR:带有子对象参数的 add 方法与调用父类的实现不匹配。有没有解决的办法?

除非有更好的方法,否则我的计划是为 Collections 创建一个接口,如下所示:

interface Collection {
   public function add( ValueObjects $obj ) : bool;
}

abstract class ValueObjects {}

class User extends ValueObjects {
}

然后我在用户值对象的具体集合上实现 Collection 接口。

class UserCollection implements Collection {
  public function add( User $user ) : bool 
  {
     return true;
  }
}

这显然会引发Declaration should be compatible with Collection 的错误。有没有办法实现相同的结果以允许签名中的子对象?我尝试了抽象类,但没有结果。

如果我不必扩展抽象的 ValueObjects 类,那就更好了。

感谢您在 SO 社区分享您的知识。

PS...评论this question,装饰器和组合模式。我要么错过了它,要么我的问题有点不同。

【问题讨论】:

  • 这就是为什么你不想这样做,即使它是可能的:你在某处得到一个Collection 对象,你想调用add 方法。界面说你接受任何ValueObjects。所以你也是。但是你得到了一个UserCollection,它只接受它的一个子类型。 Collection 合约失效,你不能通过任何ValueObjects,只能通过User。你不能相信Collection,界面没有意义; en.wikipedia.org/wiki/Liskov_substitution_principle
  • abstract ValueObjects 类的意义何在?标记接口应该是接口;样板和实际扩展属于抽象,例如 add,它很有用,除非被实现的合成器覆盖。
  • 谢谢@Federkun,这很有道理。
  • 标记接口是什么意思? @JaredFarrish 感谢您的解释
  • 标记接口将类标记为类型,但没有实现细节。假设您只想将已知类型的无副作用值提供给方法。这太多样化了,无法猜测,而且超出了抽象性。使用class User implements Valuableinterface Valuable {} 标记该类,它没有具体表达这意味着什么,只是它通过了身份检查。它不能用一个类不实现副作用的接口来证明,所以通过将其标记为这样来强制开发“契约”它所做的事情。

标签: php design-patterns


【解决方案1】:

简短的回答是“否”,因为 PHP 中缺少泛型类。

要解决这个问题,您应该在实现类中继续使用抽象类型(此处为ValueObjects),然后自行检查实际类型。

class UserCollection implements Collection {
  public function add( ValueObjects $obj ) : bool 
  {
     if (!($obj instanceof User)) {
          throw new RuntimeException('...');
     }

     /** @var User $obj */
     // The above comment make your IDE know the $obj is a `User` instance

     // your actual logic
     return true;
  }
}

一个小提示,您不需要在 PHP 中将 $obj 对象从 ValueObjects 转换为 User。上面代码中的 phpDoc 内联 @var 注释行仅告诉 IDE $objUser 实例并支持 User 方法的自动完成。没有它,PHP 脚本仍然运行。

【讨论】:

  • 包含bool 而不是void 是没有意义的,并且在没有上下文的情况下推断上下文:如果添加失败并返回false,那会怎样?异常更有意义,它避免了返回签名。
  • 我同意你的看法。 return 语句在 OP 的原始代码中,我只保留它。
【解决方案2】:

我会在这些方面提出一些建议。请注意我是如何扩展客观收集的核心概念,同时表现得像一个自我验证的主题。然后我将这些不同的结构组合成一个可知的、具体的组合。

interface Transportable {};
interface User {};
interface Collected
{
    public function isValid($item): bool;

    // Contract, add is deterministic, require no return
    public function add(Transportable $item): void;
}

trait SanityChecked
{
    public function isValid($item): bool
    {
        return true;
    }
}

abstract class Collector implements Collected
{
    use SanityChecked;

    private $items = [];

    public function add(Transportable $item): void
    {
        if ($this->isValid($item) && $this->items[] = $item) {
            return;
        }

        throw new Exception('This is the not the droid we were looking for.');
    }
}

class Users extends Collector
{
    public function isValid($test): bool
    {
        return $test instanceof User;
    }
}

可以模拟为:

$users = new Users();

$users->add(new class() implements Transportable, User {});

echo 'Added first!'.PHP_EOL;

$users->add(new class() implements User {}); // Sorry, error.

echo 'Added second!'.PHP_EOL;

https://3v4l.org/O2qfJ

另一种看待它的方式是进一步扩展特征的行为:

trait ValidatesAsUser
{
    public function isValid(Transportable $user): bool
    {
        return $user instanceof User;
    }
}

class PortalUsers extends Collector
{
    use ValidatesAsUser;
}

class ContentEditors extends PortalUsers {}

class Authors extends ContentEditors {}

class AuthorsWithPublishedStoriesByRating extends Authors {}

我认为关于预测的最后一部分特别有趣。

【讨论】:

  • 可能更有趣的是trait ValidatesContentEditorclass ContentEditors extends Users,我们的新特征中的有效性检查是return $test instanceof ContentEditor && parent::isValid($item); 的超类。然而,我怀疑一个有用的实现将有ContentEditor 扩展User 具有覆盖的具体类,但是进一步的角色链接可能会利用这种行为,例如,如果所有ContentEditors 也是PortalUsers。跨度>
  • 感谢您抽出宝贵时间来做这件事。一段时间以来,我一直在为组合而不是继承而苦苦挣扎,所以我真的很感激能帮助我克服这个障碍。 :)
  • 我试着把我的盒子想象成有设备和机器,就像在一个真正的工厂里(不是软件结构)。那么站在工位上的工匠是谁呢?你们在讨论。您必须想象、定义和理解您将哪些部分组合在一起,以及存在的角色(用户、角色等)。这是组合。用较小的部分创造一个更大的整体(例如,一篇论文是句子的组合)。
  • 另外,我意识到这是类组合,而不是对象组合。对象组合是您模块化一流类型的地方。想想“多速电动传动轴七卡盘螺丝刀”与“钻头”。一个是规范,另一个是工具。对象组合是关于为您的领域部分提供工具,使其具有单一用途。
猜你喜欢
  • 2016-01-19
  • 2014-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多