ファーストクラスコレクション使ってみた
Daily Blogging69日目
改訂新版 良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方でカプセル化について学んだときに、ファーストクラスコレクションというデザインパターンを知ったので試しにやってみた。
ファーストクラスコレクション??
コレクションをただのデータとして扱うのではなく
振る舞いを持ったオブジェクトとして扱うデザインパターン
振る舞いを持たせることで、コレクションに関するロジックを集約することができカプセル化につながる。
ファーストクラスって何???
多分ファーストクラスオブジェクト(第一級オブジェクト)から来ている
それも何って感じだけど、wikipediaによると
第一級オブジェクト(ファーストクラスオブジェクト、first-class object)は、あるプログラミング言語において、たとえば生成、代入、演算、(引数・戻り値としての)受け渡しといったその言語における基本的な操作を制限なしに使用できる対象のことである。
めちゃくちゃざっくりいうと、「データ」に対して「振る舞い」を持つオブジェクトのこと...だと思う
ファーストクラスコレクションはそれのコレクションバージョン
そもそもの問題点
ファーストクラスコレクションが使えるんじゃないかってなったのはこんなコードがカプセル化的によくないんじゃないかってなったから。
サンプルコード
class User < ApplicationRecord
has_many :user_payments
# 省略
def latest_extra_user_payment
self.user_payments.where(payment: 'extra').last
end
def latest_canceled_user_payment
self.user_payments.where(payment: 'cancel').last
end
Userクラスの中で、そのアソシエーションであるuser_paymentsに対して検索条件をゴニョゴニョしてデータを取得している。
こういうのがUserクラスにはいっぱいあってクラスが肥大化している。
今はまだUserクラスにしかないが、今後このuser_paymentsのデータ取得ロジックと同じようなロジックがあちこちに書かれる可能性もあり、保守性が下がる可能性が高い。
だからこのロジックはUserPaymentクラスにあるべきじゃないのか?
UserPaymentsクラスを作ってみたら?
書籍にもEquipmentsクラスの話があったが、それと同じように複数のuser_paymentデータを扱う責務をもったクラスを作ってそこにロジックを集約したらいいんじゃないかってなったので試しに簡易的に作ってみた
サンプルコード
class User < ApplicationRecord
has_many :user_payments
def payments
UserPayments.new(user_payments)
end
end
class UserPayments
attr_reader :user_payments
def initialize(user_payments)
@user_payments = user_payments
end
def latest_extra_user_payment
self.user_payments.where(payment:'extra').last
end
def latest_canceled_user_payment
self.user_payments.where(payment:'cancel').last
end
end
これでuser_paymentsに関するロジックがここに集約されて保守性も可読性も上がったように感じる。
UserPaymentクラスは、user_paymentの一つのレコードに対するロジックを管理する責務をになってもらう。
デメリット
カプセル化もちゃんとできていい感じだけど、全部のデータに対してファーストクラスコレクションを作るとなると、コード量が相当増えて逆に管理も大変で認知的負荷も上がる。
個人的には、コレクションに対するロジックがすでに複数存在している場合にのみ適用した方がいいかなと
最初のうちはUserPaymentクラスにscope生やすだけでも良さそう
Discussion