📦

[Laravel] Strategy パターンを使ってみる

GoF 初挑戦。

プロジェクトの概要

  • 自作の Laravel パッケージ
  • データベースのテーブル構成からモデル、コントローラー、ビューを自動生成する

現状

処理の共通化を目指すあまり、モデル、コントローラ、ビューの生成処理をすべて generate() に詰め込み、生成したいファイルを引数で指定して、メソッドの中で分岐するようにしている。

Generator.php
$generator = new Generator();
$generator->generate('model');

class Generator
{
  public function generate($type)
  {
    if ($type === 'model') {
      // モデルの生成
    } elseif ($type === 'controller') {
      // コントローラの生成
    } elseif ($type === 'view') {
      // ビューの生成
    }
  }

  // 各生成処理で使うメソッドがたくさん
  private function buildForm() {}
  private function buildRelations() {}
}

課題

  • Generatorgenerate() が巨大化した
  • 目的のメソッドを見つけるのが大変
  • どの private メソッドがどの public メソッドで使われているのかわからない
  • 機能追加も修正も時間がかかってしまう

やりたいこと

  • クラスを分割して見通しをよくする
  • 機能追加や修正をスムーズにする

モデル・コントローラ・ビューの生成処理は、流れはすべて同じで、生成処理がだけが違っていたので、Strategy パターンを使うことにした。

改善前のクラス構成

Generator の中に生成処理がすべて詰まっている。 Generator を使うのが GenerateCommand クラス。

src
 ├── Console
 │   └── GenerateCommand.php
 ├── Generator.php
 └── ServiceProvider.php

改善後のクラス構成

src
 ├── Console
 │   └── GenerateCommand.php
 ├── ControllerStrategy.php
 ├── Strategy.php
 ├── ModelStrategy.php
 ├── Generator.php
 ├── ServiceProvider.php
 ├── Table.php
 ├── ValidationStrategy.php
 └── ViewStrategy.php

Generator を次の 3 つに分割した。

  • ModelStrategy
  • ControllerStrategy
  • ViewStrategy

各クラスには、各生成処理で使うものだけを含ませる。 たとえば、ModelStrategy にはモデルの生成処理が使うメソッドだけを含ませる。

GenerateCommand では、次のようにして生成処理を呼び出す。 呼び出すメソッドはすべて generate() だけど、インスタンスを生成する際に渡すクラスによってその処理内容が決まる。

$generator = new Generator(new ModelStrategy);
$generator->generate(); // Modelの生成処理

$generator = new Generator(new ControllerStrategy);
$generator->generate(); // Controllerの生成処理

$generator = new Generator(new ViewStrategy);
$generator->generate(); // Viewの生成処理

なぜこんなことができるかというと、generate() は受け取ったインスタンスの generate() を実行するようにしているから。

class Generator
{
  private $strategy;

  public function __construct(Strategy $strategy)
  {
    $this->strategy = $strategy;
  }

  public function generate()
  {
    $this->strategy->generate();
  }
}

ちなみに、Strategy はこんな感じ。

interface Strategy
{
  public function generate();
}

ModelStrategy はこんな感じ。

class ModelStrategy implements Strategy
{
  public function generate()
  {
    // モデルの生成処理
  }

  // モデルの生成処理だけで使うメソッドなど
  private buildRelations() {}
}

まとめ

Strategy とは「戦略」という意味。 同じ問題を解くにも、いろんな解き方 (戦略) がある。

Strategy パターンは、同じ処理をいろんな方法で実現するときに、いろんな戦略を簡単に切り替えられるようにするためのデザインパターン。

たとえば今回なら、「ファイルを生成する」という共通の問題があり、 それに対して「モデル」「コントローラ」「ビュー」という異なる戦略があった。

そしてデザインパターンを活用することによって、ファイルを生成する流れは共通化しながらも、 戦略を切り替えることによって生成するファイルを簡単に選択できるようになった。