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

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

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

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

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

![Strategyパターンクラス図](https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/StrategyPatternClassDiagram.svg/500px-StrategyPatternClassDiagram.svg.png)

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

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

![laravel-strategy-ptn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245411/2fe26d56-a411-9a82-b2c7-2574094d380d.png)

## 手順
ServiceProviderにて、Strategy部分のサービス登録処理を書いていきます。
SQSはAWSより提供されているClientがあるのでそれを使います。kintoneは自前でClient用意してますが[こちら](https://github.com/ochi51)の方が良さそうです。

### Service登録
```diff: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部分です。インターフェイスとして切り出すことでロジック差し替えを簡単にすることができます。  

```php: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パターンに落としやすい形へ持っていくことができます。  

```php: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送信ロジック

```php: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送信ロジック

```php: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を呼び出します。

```php: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パターンはよく使うパターンのひとつ
少し難しかったかもしれませんが、けっこう使う機会の多いパターンだと思いますので、
複数ロジックを実装しないといけないといった時には使ってみてください。

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

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

それではまた。