Help us understand the problem. What is going on with this article?

LaravelでStrategyパターンを実装してみる【初心者向け】

同じ機能を、複数の異なる方法で実装するといった場面を考えてみてください。
愚直に実装しようとすると1ファイルに条件分岐が重なり、機能追加や変更があったときに大変です。

そこで今回はStrategyパターンという振る舞いに関するパターンをLaravelでやってみます。
Laravel触ってみるもどうやって機能追加すればいいか分からない、という方は参考になるかもしれません。

Strategyの効果

Strategy(= 戦略)部分をクラス単位でカプセル化し、コンストラクタで注入することで、ロジックの差し替えが容易になるというデザインパターンになります。

Wikipediaより

wikipediaからの引用図。Contextに対して、Strategyを注入します。

Strategyパターンクラス図

今回実装するクラス図

今回は、データ送信時にSQSとkintoneの2パターンの戦略を用いるという前提でやってみます。
下記イメージで実装をしていきます。

SqsClientKintoneClientはデータ送信用APIになります。今回はこれらの中身については扱わないのでご了承を。

laravel-strategy-ptn.png

手順

ServiceProviderにて、Strategy部分のサービス登録処理を書いていきます。
SQSはAWSより提供されているClientがあるのでそれを使います。kintoneは自前でClient用意してますがこちらの方が良さそうです。

Service登録

app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

+ use Aws\Sqs\SqsClient;
+ use App\Services\Backend\KintoneClient;
+ use App\Services\Backend\Strategy\{SendSqsStrategy, SendKintoneStrategy};

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
+        $this->app->bind(SendSqsStrategy::class, function($app) {
+            return new SendSqsStrategy(SqsClient::factory([
+                'key' => config('sqs.key'),
+                'secret' => config('sqs.secret'),
+                'QueueUrl' => config('sqs.url'),
+                'region' => config('sqs.region'),
+                'version' => config('sqs.version'),
+            ]));
+        });
+        $this->app->bind(SendKintoneStrategy::class, function($app) {
+            return new SendKintoneStrategy(new KintoneClient());
+        });
    }
}

Context部分のインターフェイス作成

wikipediaの図でというContext部分です。インターフェイスとして切り出すことでロジック差し替えを簡単にすることができます。

app/Contracts/Services/Strategy/Register.php
<?php

namespace App\Contracts\Services\Strategy;

/**
 * Strategy interface by which handle the Queue
 */
interface Register
{
    /**
     * Execute the logic
     *
     * @return void
     */
    function execute();
}

Job作成

続いてJobを作成します。
Jobを用いることで非同期処理が簡単に実装できるというのに加え、Strategyパターンに落としやすい形へ持っていくことができます。

app/Jobs/RegisterContext.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Contracts\Services\Strategy\Register;

/**
 * データ送信実行ジョブ
 */
class RegisterContext implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $strategy;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(Register $strategy)
    {
        $this->strategy = $strategy;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $this->strategy->execute();
    }
}

Strategy用Service作成

次にStrategyを実装します。実際にデータを送信するロジックがここに書かれます。

SQS送信ロジック

app/Services/Backend/Strategy/SendSqsStrategy.php
<?php

namespace App\Services\Backend\Strategy;

use Aws\Sqs\SqsClient;
use App\Contracts\Services\Strategy\Register;

class SendSqsStrategy implements Register
{
    /** @var SqsClient */
    protected $client;

    public function __construct(SqsClient $client)
    {
        $this->client = $client;
    }

    public function execute()
    {
        $this->client->sendMessage([
            'QueueUrl'    => config('sqs.url'),
            'MessageBody' => "foobarpiyohoge",
        ]);
    }
}

kintone送信ロジック

app/Services/Backend/Strategy/SendKintoneStrategy.php
<?php

namespace App\Services\Backend\Strategy;

use App\Services\Backend\KintoneClient;
use App\Contracts\Services\Strategy\Register;

class SendKintoneStrategy implements Register
{
    /** @var KintoneClient */
    protected $client;

    public function __construct(KintoneClient $client)
    {
        $this->client = $client;
    }

    public function execute()
    {
        $this->client->addRecord([
            'message' => ["value" => "foobarpiyohoge"],
        ]);
    }
}

Controllerで呼び出し

最後にコントローラーにてJobを呼び出します。

app/Http/Controllers/EntryInputController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Requests\EntryInputPost;
use App\Jobs\RegisterContext;
use App\Services\Backend\Strategy;

class EntryInputController extends Controller
{

    /**
     * process the incoming customer information post
     *
     * @param EntryInputPost $request
     * @return \Illuminate\Http\Response
     */
    public function process(EntryInputPost $request)
    {
        // SQSで送信
        $this->dispatch(new RegisterContext(app()->make(Strategy\SendSqsStrategy::class)));

        // kintoneで送信
        $this->dispatch(new RegisterContext(app()->make(Strategy\SendKintoneStrategy::class)));
     }
}

これで実装は完了です。

Strategyパターンはよく使うパターンのひとつ

少し難しかったかもしれませんが、けっこう使う機会の多いパターンだと思いますので、
複数ロジックを実装しないといけないといった時には使ってみてください。

ロジックの変更や差し替えが簡単になりますし、複数ロジックが干渉しあうことも無くなると思います。

ここが分かりにくい、ここ違うよという点があれば教えてもらえると嬉しいです。

それではまた。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした