web-dev-qa-db-ja.com

異なるが「互換性のある」署名でインターフェースメソッドをオーバーライドできますか?

次のPHPインターフェイスを検討してください:

_interface Item {
    // some methods here
}

interface SuperItem extends Item {
    // some extra methods here, not defined in Item
}

interface Collection {
    public function add(Item $item);
    // more methods here
}

interface SuperCollection extends Collection {
    public function add(SuperItem $item);
    // more methods here that "override" the Collection methods like "add()" does
}
_

PHPStormを使用していますが、これを行うと、IDEで、基本的にSuperCollectionadd()の定義がそうではないことを示すエラーが発生します。拡張するインターフェースの定義Collectionと互換性があります。

ある意味では、メソッドのシグニチャが「オーバーライド」するシグニチャと一致しないため、これが問題であることがわかります正確に。ただし、SuperItemItemを拡張するため、これは互換性があると思います。したがって、add(SuperItem)add(Item)と同じように表示されます。

これがPHP(バージョン5.4以降)でサポートされているかどうか知りたいのですが、おそらくIDEには、適切にキャッチされないバグがありますこの。

22
jzimmerman2011

いいえ、どのバージョンでもPHPはこれをサポートしておらず、インターフェイスの要点を打ち負かすだろうと確信しています。

インターフェイスのポイントは、同じインターフェイスを参照する他のコードとの固定契約を提供することです。

たとえば、次のような関数について考えてみます。

_function doSomething(Collection $loopMe) { ..... }
_

この関数は、Collectionインターフェースを実装するオブジェクトを受け取ることを想定しています。

関数内で、プログラマーはCollectionで定義されているメソッドへの呼び出しを記述でき、オブジェクトがそれらのメソッドを実装することを知っています。

このようなオーバーライドされたインターフェイスがある場合は、SuperCollectionオブジェクトが関数に渡される可能性があるため、これに問題があります。継承によりCollectionオブジェクトでもあるため、機能します。しかし、関数内のコードは、add()メソッドの定義が何であるかを認識していることを確認できなくなりました。

インターフェースは、定義上、固定契約です。それは不変です。

別の方法として、インターフェースの代わりに抽象クラスを使用することを検討できます。これにより、非厳密モードでオーバーライドできますが、同じ理由で、厳密モードを使用するとエラーが発生します。

14
Spudley

回避策として、インターフェイスでPHPDocブロックを使用しています。

interface Collection {
   /**
    * @param Item $item
    */
    public function add($item);
    // more methods here
}

interface SuperCollection extends Collection {
    /**
    * @param SuperItem $item
    */
    public function add($item);
    // more methods here that "override" the Collection methods like "add()" does
}

このように、インターフェイスを適切に使用している場合、IDEは、いくつかのエラーをキャッチするのに役立ちます。 同様の手法 を使用して、戻り値の型をオーバーライドすることもできます。

3
Nik Denisov

2014年11月にリリースされたPHP7.4には、 タイプの差異の改善 があります。

質問のコードは無効のままです。

interface Item {
    // some methods here
}

interface SuperItem extends Item {
    // some extra methods here, not defined in Item
}

interface Collection {
    public function add(Item $item);
    // more methods here
}

interface SuperCollection extends Collection {
    public function add(SuperItem $item); // This will still be a compile error
    // more methods here that "override" the Collection methods like "add()" does
}

Collectionインターフェイスは、それを実装するすべてのものがItemタイプのオブジェクトをaddのパラメータとして受け入れることができることを保証するためです。

ただし、次のコードはPHP 7.4:

interface Item {
    // some methods here
}

interface SuperItem extends Item {
    // some extra methods here, not defined in Item
}

interface Collection {
    public function add(SuperItem $item);
    // more methods here
}

interface SuperCollection extends Collection {
    public function add(Item $item); // no problem
    // more methods here that "override" the Collection methods like "add()" does
}

この場合、Collectionは、任意のSuperItemを受け入れることができることを保証します。すべてのSuperItemsはItemsであるため、SuperCollectionもこの保証を行い、他のタイプのItemを受け入れることも保証します。これは 共変性メソッドパラメータタイプ として知られています。

以前のバージョンのPHPには、限定された形式の型の差異があります。他のインターフェースが質問のとおりであると仮定すると、SuperCollectionは次のように定義できます。

interface SuperCollection extends Collection {
    public function add($item); // no problem
    // more methods here that "override" the Collection methods like "add()" does
}

これは、addメソッドに渡される可能性のある任意の値を意味すると解釈できます。もちろん、これにはすべてのItemsが含まれるため、これはタイプセーフです。または、一般にmixedとして文書化されている不特定のクラスの値が渡される可能性があり、プログラマーが次のことを行う必要があることを意味すると解釈できます。関数で何が機能するかについて、他の知識を正確に使用してください。

3
bdsl

メソッドの引数を変更することはできません。

http://php.net/manual/en/language.oop5.interfaces.php

2
Galen

インターフェイスを拡張してメソッド定義を変更することはできません。 SuperItemがItemを拡張している場合は、Collectionインターフェイスを実装するクラスを問題なく通過する必要があります。

しかし、あなたが本当にやりたいことに基づいて、あなたは試すことができます:

  • SuperItemのメソッドが少し異なるインターフェイスを作成し、それを実装します。

    interface SuperCollection extends Collection {
        public function addSuper(SuperItem $superItem);
    }
    
  • デコレータパターンを使用して、拡張せずにほぼ同じインターフェイスを作成します。

    interface Collection {
        public function add(Item $item);
        // more methods here
    }
    
    interface SuperCollection {
        public function add(SuperItem $item);
        // more methods here that "override" the Collection methods like "add()" does
    }
    

    次に、このインターフェイスを使用するデコレータ(抽象)クラス:

    class BasicCollection implements Collection {
        public function add(Item $item)
        {
        }
    }
    
    class DecoratingBasicCollection implements SuperCollection {
        protected $collection;
    
        public function __construct(Collection $collection)
        {
            $this->collection = $collection;
        }
    
        public function add(SuperItem $item)
        {
            $this->collection->add($item);
        }
    }
    
1
Peter Laboš

オーバーロードが必要になる可能性のあるメソッドがある場合(PHPはサポートしていません)、メソッド引数の1つ(通常は最後)が配列であることを確認します。このようにして、次に、関数内でさまざまな配列要素をテストして、通常はselect/caseで実行する必要のあるメソッドのルーチンを教えてくれます。

0
Craig Jacobs

問題はIDEにはありません。 PHPでは、メソッドをオーバーライドすることはできません。互換性は反対方向にのみあります。親クラスのインスタンスを安全に期待してサブクラスを受け取ることができます。ただし、サブクラスを期待する場合は、親クラスを受け取った場合は安全ではありません-サブクラスは親に存在しないメソッドを定義する可能性がありますが、それでもメソッドをオーバーライドすることはできません

0