😺

オブジェクト指向で書く。

2024/06/04に公開

はじめに

プログラミングをする時、オブジェクト指向を意識して書く事ってどれくらいあるでしょうか?
あまり意識しない事が多いと思います。

そもそも「オブジェクト指向で書く」とは、どういう事でしょう?

「いやいや、毎日オブジェクト指向でやってるよ。オブジェクト指向言語で書いてるもん。
クラス作って、new でインスタンス化(オブジェクト生成)とかやってるし。」

・・・なるほど。
確かにPHPもJavaもPythonもみんなオブジェクト指向言語です。
しかし、オブジェクト指向言語を使う事と、オブジェクト指向で書く事はちょっと違うかも?と思います。

ポリモーフィズムでスッキリと書く。

オブジェクト指向とは何かというのは研究者の間でもハッキリ定まらないほど難解な定義だとも言われていますので、ここでは私の主観で書いていきます。

私が思う、これぞオブジェクト指向の有効活用!!と言えるのが、ポリモーフィズム(多態性)の活用です。
サッカーで言えば、監督が攻めろ!って号令したら、フォワードはシュートを決める位置に向かい、ミッドフィルダーはそこへパスが出せるように狙う、でもゴールキーパーは一緒に前にいったりしない、とか。
同じ命令を受けてもそれぞれの役割に応じて別の動きをするということです。

手続き的な書き方の例

普通に書くとこうなっちゃいますかね。
よく見かけるコードです。
役割が増えるほどに分岐が増えていきます。
下の例では適当に3行づつくらいしかありませんが、もし1つの分岐が30行以上とかになるとどうでしょう?
見辛くて仕方ありませんね。

<?
class Boss {
    /**
     * 監督から書くプレイヤーの役割を確認して、監督が何をさせるかいちいち指示を出す。
     *
     * @param Player $player
     * @return void
     */
    public function fireCommand(Player $player) {

        if ($player instanceof FKPlayer) {
            $player->run(100);
            
            if($player->isKeepBall()) {
                $player->shoot();
            }
        } else if ($player instanceof MFPlayer) {
            $player->getBall();
            $player->run(30);
            $player->pathToFK();
        } else if ($player instanceof GFPlayer) {
            $player->gurd();
        }
    }
}

ポリモーフィズムを使った場合。

はい、監督のやることが随分楽になり、スッキリと見通しの良いコードになりました。
実際のコーディングでは、クラス1つにつき1ファイルになるので、今後ポジションを増やすときも元のプレイヤーのファイルをコピペして、必要なところでチョイチョイっといじれば新しい役割ができます。
そのとき、監督のコードは一切触る必要がありません。

Boss.php
<?
class Boss {
    /**
     * 監督から書くプレイヤーの役割を確認して、監督が何をさせるかいちいち指示を出す。
     *
     * @param ArrayList<Player> $players プレイヤーリスト
     * @return void
     */
    public function fireCommand(ArrayList $players) {
        
        // 監督は全員に対して頑張れというだけ。
        foreach($players as $player) {
            $player->doCommand();
        }
    }
}

プレイヤー共通の処理は基底クラスに書く。
監督の命令を受け付けるdoCommand()を子クラスが持つことを強制する。
これにより、Bossクラスが命令した時にメソッドがないからシステムエラーになるなんて心配が不要になる。

Player.php
<?
abstract class Player {
    abstract public function doCommand();
    
    public function run(int $x) {
        // 走る処理
    }

    public function shoot() {
        // シュートの処理
    }

    public function gurd() {
        // ガードの処理
    }
    // ~~ 以下、プレイヤー共通処理~~
}

フォワードならこう動くということを書く。

FKPlayer.php
class FKPlayer extends Player{
    public function doCommand() {
        $this->run(100);
            
        if($this->isKeepBall()) {
            $this->shoot();
        }
    }
}

同様にミッドフィルダーならではの動き。

MFPlayer.php
class MFPlayer extends Player{
    public function doCommand() {
        $this->getBall();
        $this->run(30);
        $this->pathToFK();
    }
}

同様にゴールキーパーならではの動き。

GFPlayer.php
class GFPlayer extends Player{
    public function doCommand() {
        $this->gurd();
    }
}

まとめ

いかがでしたか?
1つ1つ関数のコード量が激減し、見通しが良くなり、保守性・拡張性が大幅に高まり、いい事づくめなのが伝わったでしょうか?

ちなみに、このコマンドにdoCommand(Command $command)とかやると監督の指示によって動きを変える事もできます。
ここには書きませんが、詳しくGOFのデザインパターンにあるコマンドパターンはこんな感じなので興味のある人は調べてみてください。
ドラクエ風ゲームとかの基礎勉強にもなりますよ。

実際の業務では、やたら1つの関数の中でif文が多く、その分岐の条件がクラスの型になっている場合にこの手法を使うといいコードになりやすいです。

今回はdoCommandしかないので実感しずらかったかもしれませんが、抽象メソッドが10種類くらいあると想像してみてください。
元のコードのままだと、プレイヤーが増えるたびにその全てに対してメイン関数に修正を入れていくことになります。
ゾッとしますね。
でも、このやり方を使えば、メイン部分のコードは

startGame();
doCommand();
cancelCommand();
callMember();
endGame();

のように大筋の流れを書くだけになり、処理の大まかな流れは分かり易いまま維持可能です。

株式会社ONE WEDGE

【Serverlessで世の中をもっと楽しく】 ONE WEDGEはServerlessシステム開発を中核技術としてWeb系システム開発、AWS/GCPを利用した業務システム・サービス開発、PWAを用いたモバイル開発、Alexaスキル開発など、元気と技術力を武器にお客様に真摯に向き合う価値創造企業です。
https://onewedge.co.jp

Discussion