銀行員からのRailsエンジニア

銀行員からのRailsエンジニア

銀行員から転身したサービス作りが大好きなRailsエンジニアのブログです。個人で開発したサービスをいくつか運営しており、今も新しいサービスを開発しています。転職して日々感じていること、個人開発サービス運営のことなどを等身大で書いていきます。

当ブログではアフィリエイト広告を利用しています

【Rubyによるデザインパターンまとめ11】ファクトリメソッドパターン

コードの品質向上のため、Rubyデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめており、今回が第11弾です。(毎週1つが目標です!)

前回の記事(シングルトンパターンのまとめ)はこちらです。
【Rubyによるデザインパターンまとめ10】シングルトンパターン - 銀行員からのRailsエンジニア

言葉での説明よりもコードを見た方が分かりやすいので、サンプルコードでの説明をメインにしています。

今回は ファクトリメソッド(Factory Method)パターン についてまとめました。

f:id:ysk_pro:20200209075739p:plain

ファクトリメソッドパターンとは

オブジェクトの生成をファクトリメソッドに任せることで、クラス名を指定せずにオブジェクトを生成することができるデザインパターンです。

コードの責務を分割して、保守しやすいコードになることがメリットです。

ファクトリ(Factory)とは「工場」という意味で、ファクトリメソッド = オブジェクトを生産するメソッドというイメージです。

文章の説明よりも実際のコードを見た方が分かりやすので、以下のサンプルコードをご覧ください。

サンプルコード

Hashで与えられるデータを指定した形式(json または csv)で出力するプログラムを考えてみましょう。
まずはファクトリメソッドパターンを使わずに実装してみます。

require 'json'

class Report
  def self.generate(data, type)
    case type
    when 'json'
      data.to_json
    when 'csv'
      data.keys.join(',') + "\n" + data.values.join(',')
    end
  end
end

次のように実行すると

data = {age: 28, hobby: 'golf'}
puts Report.generate(data, 'json')
puts '---'
puts Report.generate(data, 'csv')

実行結果はこのようになります。

{"age":28,"hobby":"golf"}
---
age,hobby
28,golf

このコードの問題点は、Reportクラスが 以下の2つの責務を持ってしまっている点です。

  • 対応する形式(jsoncsv)の定義
  • それぞれの形式に変換するロジック

複数の責務を持ってしまっていることは、有名なSOLID原則の「S」Single Responsibility Principle:単一責任の原則 に反しますね。

このサンプルコードでは対応する形式の種類が少なく、変換のロジックもシンプルなため問題が無いように感じますが、形式の種類が増えたりロジックが複雑になると、可読性が低く保守しづらいコードになってしまいます。

これをファクトリメソッドパターンを使って書き換えてみましょう。

class Formatter
  def self.type(type) # これがファクトリメソッド!
    case type
    when 'json'
      JsonFormatter.new
    when 'csv'
      CsvFormatter.new
    end
  end
end

class JsonFormatter
  def format(data)
    data.to_json
  end
end

class CsvFormatter
  def format(data)
    data.keys.join(',') + "\n" + data.values.join(',')
  end
end

class Report
  def self.generate(data, type)
    Formatter.type(type).format(data)
  end
end

実行方法、結果は先ほどと同じです。

先ほど課題であった、Reportクラスが持ってしまっていた2つの責務を以下のように分けることができました。

  • 対応する形式の定義 → Formatterクラス
  • 変換ロジック → JsonFormatter/CsvFormatterクラス

対応する形式を増やす場合や、変換ロジックに修正が入った場合には、FormatterクラスやJsonFormatter/CsvFormatterクラスを修正すればよく、Reportクラスの修正は不要になりました。

Reportクラスの責務はレポートを出力することであり、レポート出力に関係ない変換ロジックなどの修正でReportクラスを変更しなくてよくなり、保守性が上がっていると思います。

またこれはGoFの原則の1つ「変わるものを変わらないものから分離する」にも則っています。

今回のサンプルコードは、こちらのサイトを参考にさせていただきました。英語ですがとても分かりやすくて勉強になりました。
Ruby - Factory Method pattern - Ruby Blog

おわりに

ここまで読んでいただきありがとうございます。

これまでのブログと形式を変えて、デザインパターンを使っていないコードとその問題点を提示してから、デザインパターンでその問題点を解決していく」という形式で書いてみました。
個人的にはこの方が分かりやすい気がしているので、次回からもこの形で書いていこうと思います。

Rubyによるデザインパターン の中でも、ファクトリメソッドを使っていないコードをファクトリメソッドを使って改善してく流れの説明が分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

Rubyによるデザインパターン

Rubyによるデザインパターン

次回は、ビルダ(Builder)パターンをまとめます。

来週も頑張ります!

(追記)
ビルダパターンについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com