👏

ファーストクラスコレクション使ってみた

2025/02/28に公開

Daily Blogging69日目

改訂新版 良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方でカプセル化について学んだときに、ファーストクラスコレクションというデザインパターンを知ったので試しにやってみた。

ファーストクラスコレクション??

コレクションをただのデータとして扱うのではなく
振る舞いを持ったオブジェクトとして扱うデザインパターン
振る舞いを持たせることで、コレクションに関するロジックを集約することができカプセル化につながる。

ファーストクラスって何???

多分ファーストクラスオブジェクト(第一級オブジェクト)から来ている
それも何って感じだけど、wikipediaによると

第一級オブジェクト(ファーストクラスオブジェクト、first-class object)は、あるプログラミング言語において、たとえば生成、代入、演算、(引数・戻り値としての)受け渡しといったその言語における基本的な操作を制限なしに使用できる対象のことである。

https://www.weblio.jp/content/第一級オブジェクト

めちゃくちゃざっくりいうと、「データ」に対して「振る舞い」を持つオブジェクトのこと...だと思う
ファーストクラスコレクションはそれのコレクションバージョン

そもそもの問題点

ファーストクラスコレクションが使えるんじゃないかってなったのはこんなコードがカプセル化的によくないんじゃないかってなったから。

サンプルコード

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