何年か前にRead Set Goパターンというのをウェブのどこかで見かけた記憶があって、いま検索しても見つからないので存在するかどうか怪しいのですが、これをネタにオブジェクトの設計について考えて見ます。
Ready-Set-Goパターンって?
オブジェクトの使い方を、こういう順番で考えます。
-
Ready: オブジェクトを構築する。 -
Set: 必要な情報をセットする。 -
Go: 実行!
とまぁ、これだけなんですが…
そもそも「Ready Set Go」って何?
「Ready, Set, Go」とは、アメリカンフットボールの掛け声から来ていると思います。攻撃側のQBが大声を張り上げて、自チームの攻撃のタイミングを取ります。
Ready-Set-Goで設計
なぜか逆順のGo→Set→Readyで説明…
GO!
オブジェクトにGoはひとつ
「Go」に当たる処理は、一つのオブジェクトで一つだけ、にします。
いくつも違う処理がある場合は、オブジェクトに機能が多すぎるのかもしれません。
例としてクレジットカードのチャージを考えます。例えば、こんなAPI。売上を上げる場合のオブジェクトを考えます。
$credit->charge($amount); // 実売り
ま、シンプルですよね。これなら使い方に悩むこともないですし、コードもすっきりするのではないでしょうか。
パラメーターをメソッドで表す
同じ処理だけれど、少し違う条件や値で実行する場合があります。パラメーターで渡すことが多いですが、バッサリと分かりやすいメソッドを作ってしまう手もあります。
例えば、クレジットカード処理で、最初に実売りか仮売りをするかを指定できるとします。
$credit->charge($amount, ['capture'=>false]); // 仮売り
$credit->charge($amount, ['capture'=>true]); // 実売り
falseが仮売りだったかどうか、覚えるのは面倒ですし、コードが間違っていても、見つけるのは難しい気がします。そんな場合は、
$credit->authorize($amount); // 仮売り
$credit->capture($amount); // 実売り
とメソッドを増やして、処理内容を明確にします。
Goは動詞
Goのメソッド名には動詞が一番。
PHPなら日本語でも使えます。使ったことないけど。
$credit->仮売り($amount);
$credit->実売り($amount);
Goのメソッド名としてピッタリとハマる動詞が見つかったら、オブジェクトの設計としては上出来だと思います。
SET!
Setは無い方がいい
Ready-Set-Goパターンと書いておいてなんですが、Setは無いのが一番。
例えば、先のオブジェクトをsetterで書き直すと…
$credit->setAmount($amount);
$credit->setCapture(true);
$credit->transfer();
Setがあると、オブジェクトに色々な状態が出きやすくなります。例えば合計を設定してない場合や、呼び出す順番とかでの微妙な対応でバグの可能性が増えます。
なるべく引数で必要な情報を渡すほうが、オブジェクトが単純になって使いやすいと思います。
必須のパラメターはReadyかGoで
そもそも、全ての情報は同じ重要度なのでしょうか?無いと動かない、意味がわけわからなくなる情報というのはあります。
クレジットカードなら、カード情報は必須でしょうし、金額がないというのも考えられません。そういう情報は、Readyの時(オブジェクトの構築時)、あるいはGoの実行時に渡すとすると、確実に設定できます。
$credit = new CreditCard($card_information); // クレジットカード情報
$credit->setMeta($order_id); // 注文IDをメタ情報として設定
$credit->capture($amount); // 実売りをあげる
オプションの情報だけSetしてあげる、とすると分かりやすくなると思います。
DTOを導入してみる
とは言え、大量の引数が必要な場合もあります。引数が長過ぎると、これも使いづらくなります。
そんな時は、**DTO(データ転送オブジェクト)**を導入してみるのはどうでしょう。例えばSQLビルダーだと、SQL設定とSQL文生成のオブジェクトを分離する、とかで対応します。
$transfer->setAmount($amount);
$transfer->setCapture(true);
$credit->transfer($transfer);
この例だと有り難みを感じませんが、設定する情報が増えた場合は設計がすっきりとする、はず。
READY!
ReadyするときはDIで
最初に戻ってReadyのとき。オブジェクトを構築するときは、依存するサービス(要は別のオブジェクト)を引き取ります。依存性の注入(DI)です。
注入するのはサービスだけでなく、アカウント情報といった設定値の場合もあります。
factoryとか
例として使ってきたクレジットカードですが、絶対に必要な情報として、アカウント情報、クレジットカード情報、そして金額、があると思います。
アカウント情報はアプリ構築時に決まっている情報の場合が多いと思います。一方、クレジットカード情報はフォームから入力される、あるいは合計金額はDBから値段を読みだしてから計算して求める、などなど情報が決まるタイミングが異なる場合があります。
こういう場合の対応方法として、ファクトリを上手に使うことを考えてます。それぞれの情報をファクトリに注入してゆきます。
$factory = new CreditFactory($my_account_information); // アカウント情報
$credit = $factory->createFromCard($card_information); // クレジットカード情報
$credit->capture($amount); // 実売りをあげる
どうでしょう。使うときに悩まないと思うのですが…
本当に使いやすくなったのか、実は試行錯誤中…
使いドコロ
Ready-Set-Goを考えながら設計すると、オブジェクトがシンプルで分かりやすくなって気に入ってます。
特にドメインと呼ばれるビジネスロジックだと、オブジェクトは自分で設計する、なんて場合が増えてきます。で、気が付くと、機能を追加しすぎて複雑怪奇なオブジェクトにしがちなのですが、そんなときは、Ready-Set-Goと唱えながらオブジェクトを分割してます。