🧐

FactoryMethodパターンを理解した気になる

2024/11/15に公開

※前提
PHP(Laravel)で実装しています。

デザインパターンを勉強するときに、こちらの記事をよく参考にするのですが、今回はFactory Methodパターンについて学ぶ機会があったのでまとめます。

デザインパターンの書籍や記事は体感Javaで書かれていることが多いイメージですが、私は業務でJavaをほとんど書いたことがないので、今回は言語はPHPで動かしてみます。

Factory Methodパターンってなにもの?

Factory Methodパターンは23種類あるデザインパターンのうち、生成系に分類されるデザインパターンの一種です。一言で言うと、

スーパークラスでオブジェクトを作成するためのインターフェースが決まっている。 しかし、 サブクラスでは作成されるオブジェクトの型を変更することができる。
とか、

具体的なオブジェクトの生成をサブクラスに任せることで、親クラスとサブクラスの間の疎結合を実現する。

といった説明があるのですが、いまいちピンとこなかったので、実際に書きながら説明します。結論、確かに1言でまとめると↑のようになります。笑

今回の例

今回は、「通知機能を持つアプリケーションの作成」という例で説明します。具体的には、

  • アプリケーションを作成する段階でEメール通知SMS通知の機能を実装することが決まっている。
  • 少したって、Slackによる通知もしたいという要望もあり、追加でSlack通知の実装も必要なった。
  • さらに追加の通知機能を実装することがこの先もありそう...

といった状況・要件だと仮定します。
つまり作成する機能は

  1. Eメール通知機能
  2. SMS通知機能
  3. Slack通知機能
  4. その他通知機能

です。これをFactory Methodパターンを利用して実現していきます。

実装例

まず、完成系のコードを下図に示します。(PHP(Laravel)で試しています。)

スクリーンショット 2024-06-22 17.32.59.png

細かいディレクトリ構成は一旦置いておいて、まずapp/Notificationsディレクトリを作成してNotification.php(インターフェース)と、 〇〇Notification.php(実装クラス)を用意します。
インターフェースはsend(string $message)メソッドを持ち、引数のメッセージを受け取って send(送る)機能を持ちます。

<?php

namespace App\Notifications;

interface Notification
{
    public function send(string $message);
}

その他の実装クラス(例えばEmailNotification)ではNotificationインターフェースを委譲して、send()メソッドを実装します。今回はわかりやすくするためにロジックは単純にecho $message;とします。

<?php

namespace App\Notifications;

class EmailNotification implements Notification
{
    public function send(string $message)
    {
        echo "Sending Email: " . $message;
    }
}

次にapp/Factoriesディレクトリを作成して、そこに NotificationFactory.php(抽象クラス)と〇〇NotificationFactory.php(具象クラス)を作成します。抽象クラスではcreateNotification()(抽象メソッド)とsendNotification()(のちにコントローラー側から呼び出す)メソッドを定義しておきます。

<?php

namespace App\Factories;

use App\Notifications\Notification;

abstract class NotificationFactory
{
    // 戻り値はNotificationインターフェース
    abstract public function createNotification(): Notification;

    public function sendNotification(string $message)
    {
        $notification = $this->createNotification();
        $notification->send($message);
    }
}
<?php

namespace App\Factories;

use App\Notifications\Notification;
use App\Notifications\EmailNotification;

// NotificationFactoryクラスを継承
class EmailNotificationFactory extends NotificationFactory
{
    // createNotification()をオーバーライドしてEmailNotificationインスタンスを返す
    public function createNotification(): Notification
    {
        return new EmailNotification;
    }
}

最後にコントローラー側から呼んであげます。(route/api(web).phpは追加しておいてください)(ResourceクラスやRequestクラスは省いてます)

<?php

namespace App\Http\Controllers;

use App\Factories\EmailNotificationFactory;
use App\Factories\SMSNotificationFactory;
use App\Factories\SlackNotificationFactory;
use Illuminate\Http\Request;

class NotificationController extends Controller
{
    public function sendEmail(Request $request)
    {
        $factory = new EmailNotificationFactory;
        $factory->sendNotification('Hello via Email!');
    }

    // public function sendSMS(Request $request)
    // {
    //     $factory = new SMSNotificationFactory();
    //     $factory->sendNotification('Hello via SMS!');
    // }

    // public function sendSlack(Request $request)
    // {
    //     $factory = new SlackNotificationFactory();
    //     $factory->sendNotification('Hello via Slack!');
    // }
}

コントローラ側ではEmailNotificationFactoryをインスタンス化して$factoryに入れています。
その後、$factory->sendNotification(message);でオーバーライドしたcreateNotification()を呼び出し、さらにsend()でechoされるという流れですね。

実際にルートを定義して、Email,SMS、Slackのパターンを作成してみたのですが、ちゃんと表示されました。

スクリーンショット 2024-06-22 17.49.19.png

スクリーンショット 2024-06-22 17.49.26.png

スクリーンショット 2024-06-22 17.49.35.png

流れ

  1. まずコントローラー側でsendEmail()が呼ばれるとEmailNotificationFactoryをインスタンス化します。(ここでまず〇〇NotificationFactoryをインスタンス化する)
  2. EmailNotificationFactoryクラスはNotificationFactoryを継承したクラスで、createNotification()をオーバーライドして、return new EmailNotification;しています。このサブクラスで Emailのオブジェクトを生成します。(←これが「具体的なオブジェクトの生成をサブクラスに任せる」という部分です)
  3. ↑のよってsendNotification()$notification = $this->createNotification();の部分で$notificationの部分には、EmailNotificationインスタンスが入ってきます。
  4. $notification->send($message);EmailNotificationクラスのsend()メソッドが呼ばれecho "Sending Email: " . 〇〇が実行されます。

メリット

  • SOLID原則である、"単一責任原則"を守ることができる
    Factory Methodパターンを使用するとスーパークラスのNotificationFactoryは通知の共通の操作(sendNotification)に集中していて、サブクラス(EmailNotificationFactory)は具体的な通知の生成に集中しています。
    これによって、各クラスが単一の責任に集中し、シンプルになります。

  • 依存性を逆転させることができる
    これもSOLID原則の1つですが、依存性逆転の原則を守ることができます。具体的な通知方法(Eメール、SMS、Slackなど)に依存せず、抽象クラスNotificationに依存することで、コードの柔軟性と拡張性が向上します。(Laravelの場合は、サービスコンテナでもDIを実現できます)


  • 新しいサブクラスの追加される時に楽になる
    この場合、新しい通知方法が追加された場合に、〇〇NotificationFactoryクラスを作成するだけで済みます。既存のコードに影響を与えずに新機能を追加できるため、拡張が容易になります。

まとめ

Factory Methodパターンを使うことで、オブジェクト生成の責任をサブクラスに委譲し、コードの柔軟性や拡張性を高めることができます。特に、将来的に通知方法が追加される可能性がある場合には、このパターンを使用することで、コードのメンテナンス性を向上させることができます。似たようで違う考え方でStrategyパターンやTemplate methodパターンもあると思うのでそちらも追々まとめていきたいと思います。

GitHubで編集を提案

Discussion