ナガモト の blog

Full Cycle Developerを目指すエンジニアが有用そうな技術記事や、ポエムのようなよしなしごとを投稿するブログです。

最も汎用的なパターン「完全コンストラクタ(Complete Constructor)パターン」の紹介

好きなパターンはなんですか?と聞かれたら私が真っ先に答えるのは「完全コンストラクタ(Complete Constructor)パターン」です。汎用的かつ効果的なパターンなので、多くの人に知ってもらいたくて記事を書きました。

「完全コンストラクタ(Complete Constructor)パターン」とは?

コンストラクタですべてのプロパティ(メンバ変数)を設定し終える(完全な状態)を作るパターンです。setterは作らず、プロパティがコンストラクタの実行後に変わることはありません。

簡単な例

四則演算を行うCalculatorClassをRubyで書くと次のようになります。*1プロパティをinitilizeで設定した後は一切変更していません。

class Calculator
  attr_reader :operand1, :operand2

  def initialize(operand1:, operand2:)
    @operand1 = operand1; @operand2 = operand2
  end

  def sum
    operand1 + operand2
  end

  def difference
    operand1 - operand2
  end

  def product
    operand1 * operand2
  end

  def quotient
    operand1 / operand2
  end
end

※attr_readerはgetterメソッドのようなものをまとめて定義してくれると思ってください。詳しくはこちら

長所

  • 可読性が高まる
  • 単一責任原則(SRP)を守りやすい

可読性が高まる理由

完全コンストラクタに則ったクラスはその性質上、IF(インターフェース)がシンプルになりクラス内の見通しがよくなります。また、クラスを呼び出す側においても、プロパティをまとめて入力してインスタンス化し、目的のメソッドを実行するというシンプルな流れになります。そこかしこでsetterが呼ばれ、状態が変わることがないため非常に見通しがよくなります。

ccc = CompleteConstructorClass.new(x, y, z) 
ccc.do

単一責任原則(SRP)を守りやすい理由

操作に必要なプロパティすべてをまとめて入力するため、多種多様な責務をクラスに持たせることが難しいです。*2また、インスタンス化後にプロパティを変更できないため、メソッド実行結果は(基本的に)同じ結果を返します。そのためメソッド実行後にインスタンスを使い捨てることが多く、使い回して多数の責務を持つような実装にはしづらくなります。一度インスタンス化してしまえば、メソッド実行結果は同じものを返すため、テストも非常に行いやすいです。

具体例: Finderパターンを完全コンストラクタで実装

Railsで構築されたあるSNSアプリで、ユーザは氏名・生年月日・性別・出生都道府県・現住都道府県のような情報を持つ。そのアプリでユーザを複雑な条件で絞り込みたいときに(Finderパターン)を使うとして、それを完全コンストラクタで実装します。

class UserFinder
  attr_reader :gender, :age, :today, :born_in_prefecture, :living_in_prefecture

  def initialize(gender: nil, age: nil, born_in_prefecture: nil, living_in_prefecture: nil)
    @gender = gender; @age = age; @today = Date.current
    @born_in_prefecture = born_in_prefecture; @living_in_prefecture = living_in_prefecture
  end

  def find
    query = User.all
    query = query.where(gender: gender) if gender
    query = query.where(born_in_prefecture: born_in_prefecture) if born_in_prefecture
    query = query.where(born_in_prefecture: living_in_prefecture) if living_in_prefecture
    query = query.where(birth_day: period_covered) if age

    return query
  end

  private

    def period_covered
      from_date = today - (age + 1).year + 1.day
      to_date = today - age
      from_date..to_date
    end
end

このように検索の実装部分が多少複雑でも、利用時は次のとおりで「大阪生まれで今東京に住んでいる20才」を検索するということが一目瞭然になります。

result = UserFinder.new(age: 20, born_in_prefecture: "大阪", living_in_prefecture: "東京").find

Finderパターン以外にのデザインパターンとも相性がよく組み合わせて使用することでよりよい設計ができるようになります。

総括

「完全コンストラクタ(Complete Constructor)パターン」はシンプルなパターンで覚えやすいのに、汎用的かつ効果的なのでぜひ利用してください!

おまけ: デザインパターンに関する参考資料

オブジェクト指向における再利用のためのデザインパターン

オブジェクト指向における再利用のためのデザインパターン

www.techscore.com masato-hi.hatenablog.jp

*1:Mathモジュール使えとか野暮なことは言わないでください

*2:多種多様な責務を果たすにはインスタンス化する際に大量のプロパティをまとめて入力しなければならないため