PHP

抽象クラスを継承した抽象クラス(abstract class Sub extend Super)は抽象メソッドを実装しなくてよい

どういうこと

<?php

abstract class Super
{
    abstract public function requiredFunc();
}

abstract class AbstractSub extends Super
{
    // requiredFuncを実装しなくてもいい
}

class Sub extends Super /* or AbstractSub */
{
    public function requiredFunc()
    {
        // 実装しないといけない
    }
}

PHP: クラスの抽象化 - Manual

抽象クラスから継承する際、親クラスの宣言で abstract としてマークされた 全てのメソッドは、子クラスで定義されなければなりません。

とあったので、子の抽象クラスでも親のメソッドの定義をしなければならないのかな?と思い、サンプルがすぐに見つからなかったので書いた。

そもそもこんな使い方が変だという可能性が大だし、引用は暗黙的に子クラスは抽象クラスでないという前提であることは飲み込める。

何がしたいのか

抽象クラスで一部処理を共通化出来て、子クラスをいろいろ作ったけど、一部の具象メソッドの定義が同じでコピペになる子クラスが多いなー。でも別の定義になる子クラスもあるしなー。

というときに、デザインパターンを使わずに継承を重ねてグループを細分化する。

共通した実装ということでtraitで一回書いてuseでもよさそうだが、適切そうな名前で細分化できる現状は横断的なtraitよりは抽象クラスなのかなーと思う。

<?php

abstract class People
{
    abstract public function nationality();

    abstract public function name();

    public function introduction()
    {
        echo $this->nationality() . 'の' . $this->name() . 'です';
    }
}


class Jon extends People
{
    public function nationality()
    {
        return 'アメリカ';
    }

    public function name()
    {
        return 'ジョン';
    }
}

class Suzuki extends People
{
    public function nationality()
    {
        return '日本';
    }

    public function name()
    {
        return '鈴木';
    }
}

class Tanaka extends People
{
    public function nationality()
    {
        return '日本';
    }

    public function name()
    {
        return '田中';
    }
}

適切な例えができなくてとてもひどいが、上の例で日本がかぶっている。
実際はDBやファイル操作などで途中まで・後始末が同じ操作のものみたいに思ってもらえればいい。

<?php

abstract class People
{
    abstract public function nationality();

    abstract public function name();

    public function introduction()
    {
        echo $this->nationality() . 'の' . $this->name() . 'です';
    }
}

abstract class Japanese extends People
{
    public function nationality()
    {
        return '日本';
    }
}


class Jon extends People
{
    public function nationality()
    {
        return 'アメリカ';
    }

    public function name()
    {
        return 'ジョン';
    }
}

class Suzuki extends Japanese
{
    public function name()
    {
        return '鈴木';
    }
}

class Tanaka extends Japanese
{
    public function name()
    {
        return '田中';
    }
}

で重複をなくせた…?
もちろんこれは処理の分岐がごく少なくて、それぞれの処理が独立している場合のみコピペをなくせるわけで。
既存コード群から異色にならないレベルで便利に作っていくのに力量不足と相まって悩む。


実際に何しているかメモっておくと、Laravelのmigrationでよく変更があるテーブルの処理をコピペしてたのをやめようとしようとしていた。

DB接続&テーブル指定
->全処理共通

追加
->up(migrate)でINSERT down(rollback)でDELETE

削除
->追加のup downを反対側に書く

更新
->up down両方UPDATE

というときに、
DB接続&テーブル指定の実装までと、固有の操作対象レコードを返す抽象メソッドをSuperな抽象クラス。
それぞれのup, downをSuper継承の各Subクラスに書こうとしていた。

使うときはSubクラスを継承して、Superの残りの抽象クラスでレコード内容を実装するだけで(up down実装せず)動くぜ!みたいなことをしたかった。

大仰にしたくないというか、Laravelが吐き出すmigrateテンプレートにextend Subを追加するだけでだいたい便利に動くかんじにしたかった。
継承ではなくup,down内でクラスを呼び出してhogehoge~する処理にしたら、
その数行が今後のコピペ対象になって、短くはなる。
だけど縛りもないし今後書き方がブレてくるかもなといらぬ心配をしたので、up,downを書かない方向は無いかなと考えた。