ポリシーパターン
はじめに
本記事では「良いコード 悪いコードで学ぶ設計入門」で学んだ内容と別途気になって調べた内容や知識も含めアウトプットしています。
書籍に記載されている内容を順序立ててまとめるというよりは、整理しておきたい周辺の知識と深ぼった内容を雑多に書いています。
書籍ではJavaで説明されていたのですが本記事ではRubyに置き換えながら解説しています。
ネストで複雑化した分岐コード
多重にネストし複雑化した分岐の解消にも役立ちます。
ECサイトにおいて、優良顧客かどうかを判定するロジックです。
顧客の購入履歴を調べ、次の条件をすべて満たす場合にゴールド会員と判定します。
- これまでの購入金額が10万円以上であること
- 1か月あたりの購入頻度が10回以上であること
- 返品率が0.1%以内であること
# ゴールド会員かどうかを判定するメソッド # @return [Boolean] ゴールド会員である場合はtrue # @param history [PurchaseHistory] 購入履歴 def is_gold_customer?(history) if history.total_amount >= 100000 # これまでの購入金額が10万円以上であること if history.purchase_frequency_per_month >= 10 # 1か月あたりの購入頻度が10回以上であること if history.return_rate <= 0.001 # 返品率が0.1%以内であること return true end end end return false end
次の条件をすべて満たす場合にシルバー会員と判定します。
- 1か月あたりの購入頻度が10回以上であること
- 返品率が0.1%以内であること
# 会員ランクを判定するメソッド # @return [Boolean] シルバー会員である場合はtrue # @param history [PurchaseHistory] 購入履歴 def is_silver_customer?(history) if history.purchase_frequency_per_month >= 10 if history.return_rate <= 0.001 return true end end return false end
会員ランクの判定条件が一部ゴールド会員と同じであり、将来的にブロンズなどの会員ランクが追加され、同様の判定条件が存在する場合、現在の実装では同じ判定ロジックが複数の場所に散らばってしまいます。
このような場合、保守性が低下するため判定ロジックの再利用性を高める方法が求められます。
再利用性を高めるためには、共通の判定ロジックを別の場所にまとめ、必要な箇所から呼び出せるようにする必要があります。
ポリシーパターンで条件を集約する
条件の部品化、部品化した条件を組み替えてのカスタマイズを可能にします。
# 優良顧客のルールを表現するインターフェース module ExcellentCustomerRule # 条件を満たす場合はtrueを返す def ok?(history) # 実際の条件判定ロジックを実装する必要があります end end
ルール
優良顧客のルールを表現するExcellentCustomerRuleモジュールをincludeしたクラスを定義します。
- ゴールド会員の購入金額ルールを表す
GoldCustomerPurchaseAmountRule
クラス - 購入頻度のルールを表す
PurchaseFrequencyRule
クラス - 返品率のルールを表す
ReturnRateRule
クラスです。
これらのクラスはExcellentCustomerRule
インターフェースを実装しており、**ok**?
メソッドを持っています。
# ゴールド会員の購入金額ルール class GoldCustomerPurchaseAmountRule include ExcellentCustomerRule def ok?(history) history.total_amount >= 100_000 end end # 購入頻度のルール class PurchaseFrequencyRule include ExcellentCustomerRule def ok?(history) history.purchase_frequency_per_month >= 10 end end # 返品率のルール class ReturnRateRule include ExcellentCustomerRule def ok?(history) history.return_rate <= 0.001 end end
Policyクラス
RubyにおけるPolicyクラスは、特定のビジネスルールや方針を表現するためのクラスです。
通常、条件判定や許可/禁止の制御ロジックをカプセル化し、再利用可能で柔軟な方法でアプリケーションの振る舞いを設定します。
優良顧客の方針を表現するExcellentCustomerPolicy
クラスを定義します。
このクラスは、複数の優良顧客ルールを保持し、そのルールをすべて満たすかどうかを判定します。
ExcellentCustomerPolicy
クラスは、@rules
というインスタンス変数でルールの集合を管理しています。
# 優良顧客の方針を表現するクラス class ExcellentCustomerPolicy def initialize @rules = Set.new # @rulesを空の**Set**オブジェクトで初期化 end # ルールを追加する def add(rule) @rules.add(rule) # 引数として与えられたルールを**@rules**に追加 end # 全てのルールを満たすか判定する def comply_with_all?(history) @rules.all? { |rule| rule.ok?(history) } # 各ルールに対して呼び出し全てのルールが満たされているか end end
作成したPolicyの使い方
ExcellentCustomerPolicy
を作成し、ゴールド会員の条件判定
gold_customer_policy = ExcellentCustomerPolicy.new gold_customer_policy.add(GoldCustomerPurchaseAmountRule.new) gold_customer_policy.add(PurchaseFrequencyRule.new) gold_customer_policy.add(ReturnRateRule.new) # purchaseHistoryを条件判定にかける if gold_customer_policy.comply_with_all?(purchase_history) # ゴールド会員の処理 puts "この顧客はゴールド会員です。" else # ゴールド会員ではない顧客の処理 puts "この顧客はゴールド会員ではありません。" end
上記ノ通りif文が一つだけとなりロジックが単純化しました。
ただ、この書き方でどこかのクラスにベタ書きしてしまうと、ゴールド会員以外の無関係なロジックを挿し込まれる可能性があるためまだ不安定な構造です。
会員ごとのポリシーを作成しロジックを集約する
ゴールド会員の条件をまとめたクラス
ExcellentCustomerPolicy
のインスタンスを作成し、そのインスタンスにGoldCustomerPurchaseAmountRule
、PurchaseFrequencyRule
、ReturnRateRule
のインスタンスを追加しています。
これにより、ゴールド会員の判定条件が設定されます。
# ゴールド会員の方針を表現するクラス class GoldCustomerPolicy def initialize @policy = ExcellentCustomerPolicy.new @policy.add(GoldCustomerPurchaseAmountRule.new) @policy.add(PurchaseFrequencyRule.new) @policy.add(ReturnRateRule.new) end # 条件判定を行うメソッド def comply_with_all?(history) @policy.comply_with_all?(history) end end
シルバー会員の条件をまとめたクラス
SilverCustomerPolicy
の初期化メソッドでは、ExcellentCustomerPolicy
のインスタンスを作成し、そのインスタンスにPurchaseFrequencyRule
、ReturnRateRule
のインスタンスを追加しています。
これにより、シルバー会員の判定条件が設定されます。
# シルバー会員の方針を表現するクラス class SilverCustomerPolicy def initialize @policy = ExcellentCustomerPolicy.new @policy.add(PurchaseFrequencyRule.new) @policy.add(ReturnRateRule.new) end # 条件判定を行うメソッド def comply_with_all?(history) @policy.comply_with_all?(history) end end
今後それぞれの会員の条件に変更があれば、このGoldCustomerPolicyやSilverCustomerPolicyだけ変更すれば良くなります。
参考
続く…
コメント
本記事の内容は以上になります!
書籍の続きのアウトプットも随時更新したいと思います。
プログラミングスクールのご紹介 (卒業生より)
お世話になったプログラミングスクールであるRUNTEQです♪
こちらのリンクを経由すると1万円引きになります。
RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。
もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。