代码之家  ›  专栏  ›  技术社区  ›  jason

在扩展抽象类时,是否有方法重新定义子类的类型提示?

php
  •  24
  • jason  · 技术社区  · 16 年前

    我将使用以下示例来说明我的问题:

    class Attribute {}
    
    class SimpleAttribute extends Attribute {}
    
    
    
    abstract class AbstractFactory {
        abstract public function update(Attribute $attr, $data);
    }
    
    class SimpleFactory extends AbstractFactory {
       public function update(SimpleAttribute $attr, $data);
    
    }
    

    Declaration of SimpleFactory::update() must be compatible with that of AbstractFactory::update()

    我完全理解这意味着什么:那 SimpleFactory::update() 的方法签名必须与其父抽象类的签名完全匹配。

    但是,我的问题是:有没有办法允许具体的方法(在这种情况下, SimpleFactory::update() )要将类型提示重新定义为原始提示的有效子代?

    一个例子是 instanceof 运算符,在以下情况下将返回true:

    SimpleAttribute instanceof Attribute // => true
    

    我确实意识到,作为一种解决方法,我可以在具体方法中使用相同的类型提示,并在方法体本身中执行instanceof检查,但是有没有一种方法可以简单地在签名级别强制执行?

    2 回复  |  直到 16 年前
        1
  •  21
  •   outis    13 年前

    我不希望如此,因为它会破坏类型暗示合同。假设一个函数 foo

    function foo(AbstractFactory $maker) {
        $attr = new Attribute();
        $maker->update($attr, 42);
    }
    ...
    $packager=new SimpleFactory();
    foo($packager);
    

    update 并将属性传递给工厂,因为 AbstractFactory::update 方法签名承诺它可以接受一个属性。砰!SimpleFactory有一个无法正确处理的对象类型。

    class Attribute {}
    class SimpleAttribute extends Attribute {
        public function spin() {...}
    }
    class SimpleFactory extends AbstractFactory {
        public function update(SimpleAttribute $attr, $data) {
            $attr->spin(); // This will fail when called from foo()
        }
    }
    

    在契约术语中,子类必须遵守其祖先的契约,这意味着函数参数可以得到更基本/更少指定/提供较弱的契约,返回值可以得到更多派生/更多指定/提供更强的契约。这一原则在《埃菲尔铁塔》(可以说是合同语言中最流行的设计)中进行了描述 An Eiffel Tutorial: Inheritance and Contracts contravariance and covariance 分别地

    从理论上讲,这是LSP违规的一个例子。不,不是 那个 LSP Liskov Substitution Principle ,表示子类型的对象可以替换为超类型的对象。 SimpleFactory 是的一个子类型 AbstractFactory ,及 需要 抽象工厂模式 应该休息一下

        2
  •  4
  •   The Prophet    12 年前

    在回答OP时,公认的答案是正确的,即他试图做的事违反了Liskov替代原则。OP应该利用一个新的接口,使用组合而不是继承来解决他的函数签名问题。OP示例问题中的变化相当小。

    class Attribute {}
    
    class SimpleAttribute extends Attribute {}
    
    abstract class AbstractFactory {
        abstract public function update(Attribute $attr, $data);
    }
    
    interface ISimpleFactory {
        function update(SimpleAttribute $attr, $data);
    }
    
    class SimpleFactory implements ISimpleFactory {
       private $factory;
       public function __construct(AbstractFactory $factory)
       {
           $this->factory = $factory;
       }
       public function update(SimpleAttribute $attr, $data)
       {
           $this->factory->update($attr, $data);
       }
    
    }
    

    上面的代码示例做了两件事:1)创建ISimpleFactory接口,所有依赖于SimpleAttributes工厂的代码都将针对该接口进行编码;2)使用泛型工厂实现SimpleFactory将需要SimpleFactory通过构造函数获取它将在中使用的AbstractFactory派生类的实例ISimpleFactory接口方法中的更新函数在SimpleFactory中重写。

    这允许从依赖ISimpleFactory的任何代码封装对泛型工厂的任何依赖,但允许SimpleFactory替换从AbstractFactory派生的任何工厂(满足LSP),而无需更改其任何代码(调用代码将提供依赖)。ISimpleFactory的一个新派生类可能决定不使用任何AbstractFactory派生的实例来实现自己,所有调用代码都将被屏蔽掉该细节。

    继承可能有很大的价值,但是,有时组合会被忽略,这是我减少紧耦合的首选方法。

        3
  •  1
  •   l0 Mag    6 年前

    由于PHP7.4,允许通过使用接口在子类中使用更具体的类型提示

    interface OtherAttributeCompatibleInterface{};
    interface SimpleAttributeCompatibleInterface{};
    
    interface AllAttributesInterface extends OtherAttributeCompatibleInterface, SimpleAttributeCompatibleInterface{};
    
    
    class Attribute implements AllAttributesInterface{};
    
    class SimpleAttribute extends Attribute  implements SimpleAttributeCompatibleInterface{};
    
    
    abstract class AbstractFactory  {
        abstract public function update(AllAttributesInterface $n);
    }
    
    class SimpleFactory  extends AbstractFactory  {
      public function update (SimpleAttributeCompatibleInterface $n){
      }
    }
    

    推荐文章