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

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

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

Rubyによるデザインパターンのまとめ【各パターンをオリジナルのコードで説明】

こんにちは。

「綺麗なコードを書く」って難しくないですか?僕は結構悩んでます。

そんな「コードの品質向上」という僕の課題に対し、マネージャーからデザインパターンを勉強してみては」とアドバイスをいただき、「Rubyによるデザインパターン」で紹介されているデザインパターンを毎週1つずつアウトプットしはじめました。

やりはじめてから5ヶ月経ち、 Rubyによるデザインパターン で紹介されている16パターン全てをアウトプットし終えたので、まとめておこうと思います。

ほとんどの記事の中でオリジナルのコードを使って説明しているので、気になるデザインパターンがあれば見てみてください。文章での説明よりもコードを見た方がわかりやすいと思います。

f:id:ysk_pro:20200321134222p:plain

 

デザインパターンの概要

Template Method パターン

基底クラスに骨格となる抽象的な処理を書き、サブクラスに具体的な処理を定義するパターンです。

アルゴリズムに多様性を持たせたい場合に便利なパターンで、不変となる部分は基底クラスに書き、変わる部分はサブクラスのメソッドに定義します。

Javaなどでサポートされている抽象メソッドや抽象クラスはRubyではサポートされていないので、呼び出した時に例外を投げるようにして擬似的に抽象メソッドを実装します。

最近ハマっている筋トレネタのサンプルコードで説明しています → 【Rubyによるデザインパターンまとめ1】テンプレートメソッド / Template Method - 銀行員からのRailsエンジニア

 

Strategyパターン

アルゴリズムの変化する部分をクラスに閉じ込めて、アルゴリズムを実行する際はそのクラスに処理を委譲するパターンです。

アルゴリズムに多様性を持たせたい場合に利用するのはTemplateメソッドパターンと同じですが、Strategyパターンは継承を使わずに実現することができます。(Templateメソッドパターンは継承を使っており、継承にはサブクラスがスーパークラスに依存してしまうというデメリットがあります)

Strategyパターンの「Strategy(戦略)」は、アルゴリズムのことです。Strategyパターンという名前は、変化するアルゴリズム(=Strategy)を、クラスに閉じ込めてそのアルゴリズムを使うオブジェクトに引き渡すことからこの名が付けられたそうです。

僕の好きなスポーツネタのサンプルコードで説明しています →【Rubyによるデザインパターンまとめ2】ストラテジーパターン - 銀行員からのRailsエンジニア

 

Observerパターン

あるオブジェクトの状態が変化した時に、そのオブジェクトが変化したことを知る必要があるオブジェクトに通知をすデザインパターンです。

Rubyによるデザインパターン では次のように説明されていました。

GoFは「何らかのオブジェクトが変化した」というニュースの発信者と消費者の間に綺麗なインターフェイスを作るアイデアを、Observerパターンと呼んでいます。

GoFとは、ギャング オブ フォーの略でデザインパターンを広めた「オブジェクト指向における再利用のためのデザインパターン」の4人の著者のことです)

変化したという通知を受け取るオブジェクトのことを「Observer」(観察者)と呼ぶことから、Observerパターンと名付けられました。

ECサイトの通知を題材にしたサンプルコードで説明しています →【Rubyによるデザインパターンまとめ3】オブザーバーパターン - 銀行員からのRailsエンジニア

 

Compositeパターン

あるものと、それが集まってできたものを同じように扱うことができるデザインパターンです。

例えば、会社は人が集まって課ができており、課が集まって部ができており、部が集まって会社ができています。人が会社から給料を受け取っているのと同じように、人の集まりである課や部も会社から給料を受け取っていると考えることができ、人・課・部が受け取っている給料を共通のインターフェースで扱うことができるイメージです。

Rubyによるデザインパターン のこちらの説明はわかりやすかったです。

GoF「全体が部分のように振る舞う」という状況を表すデザインパターンを、Compositeパターンと呼んでいます。Compositeパターンは、階層構造やツリー構造のオブジェクトを作りたいとき、そしてそのツリーを利用するコードが1つの単純なオブジェクトを扱っているのか、それともごちゃごちゃした枝全体を扱っているのかを考えさせたくないときに利用できます。

親となるオブジェクトのメソッドを呼び出すことで、子オブジェクトの同じメソッドを再帰的に呼び出すケースでよく使われます。会社の例だと、部の給料を求めるメソッドを呼び出すことで、課・人の給料を求めるメソッドを呼び出し、それを集計して値を返すイメージです。

Composite(コンポジット)とは、「複合的」という意味の単語です。

最近ハマっているゴルフネタのサンプルコードで説明しています →【Rubyによるデザインパターンまとめ4】Compositeパターン - 銀行員からのRailsエンジニア

 

Iteratorパターン

一言で言うと、オブジェクトの集まりがあった時に、そのオブジェクト1つずつに順番にアクセスする方法を提供するデザインパターンです。

GoFは、以下のように説明しています。

集約オブジェクトがもとにある内部表現を公開せずに、その要素に順にアクセスする方法を提供する

配列で与えられたデザインパターンを1つずつ出力するというシンプルなサンプルコードで説明しています → 【Rubyによるデザインパターンまとめ5】イテレータパターン - 銀行員からのRailsエンジニア

 

Commandパターン

処理の内容をオブジェクトに閉じ込めて、実行する際はそのオブジェクトのメソッドを呼び出すパターンです。

処理の内容が書いたオブジェクトのことを、命令という意味の「コマンド」と呼んでいます。

これにより複数のコマンドをキューに入れて順に実行するようにしたり、処理の取り消しなどをシンプルに実装できるようになります。

当時していた勉強内容ネタのサンプルコードで説明しています →【Rubyによるデザインパターンまとめ6】コマンドパターン - 銀行員からのRailsエンジニア

 

Adapterパターン

必要なインターフェースと既存のオブジェクトのインターフェースの違いを吸収するデザインパターンです。

以下の「アダプタ」という言葉の説明そのままの役割です。

アダプタとは、異なる複数の機器に接続する際に用いられる中間装置の総称である。(IT用語辞典バイナリ より)

RailsActiveRecord の中でアダプターパターンが使われている箇所があったので、その箇所をコードリーディングしながら説明しています → 【Rubyによるデザインパターンまとめ7】アダプターパターン - 銀行員からのRailsエンジニア

 

Proxyパターン

本来呼び出したいオブジェクトとの間にオブジェクトを挟むことで、様々な処理を差し込むことができる デザインパターンです。

言い換えると、本来呼び出したいオブジェクトを別のオブジェクト経由で呼び出し、別のオブジェクトに様々な処理を追加することができます。

インターフェースは、本来呼び出したいオブジェクトと同じにします。

よく使用されるケースとしては、アクセス制御 や 生成コストのかかるオブジェクトのインスタンス化遅延 などがあり、これらをプロキシ(挟んだオブジェクト)側で実装することで、関心事を分離することができます。(アクセス制御などが本来呼び出したいオブジェクトの関心事ではない場合に、それをプロキシ側に切り出すことができます)

プロキシ(Proxy)は「代理」という意味であり、中継サーバである「プロキシサーバ」と同じイメージです。

商品の購入、商品へのコメントができるサンプルコードで説明しています → 【Rubyによるデザインパターンまとめ8】プロキシパターン - 銀行員からのRailsエンジニア

 

Decoratorパターン

既存のオブジェクトに、機能を簡単に追加するためのパターンです。

層状に機能を積み重ねることができ、状況ごとに必要な機能のみを持つオブジェクトを作ることができます。

Decorator という単語は「装飾者」という意味で、元のオブジェクトに必要な機能を装飾するイメージです。

様々な方法で通知を行うサンプルコードで説明しています → 【Rubyによるデザインパターンまとめ9】デコレータパターン - 銀行員からのRailsエンジニア

 

Singletonパターン

ただ1つのインスタンスしか持てないクラスを作り、その1つのインスタンスへのアクセスをグローバルに提供するパターンです。

Singletonという単語は、トランプの一枚札(唯一存在するカード)という意味で、インスタンスが1つしか存在しないことを表しています。

ジムでダンベルの貸し借りを管理するサンプルコードで説明しています → 【Rubyによるデザインパターンまとめ10】シングルトンパターン - 銀行員からのRailsエンジニア

 

Factory Methodパターン

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

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

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

Hashで与えられるデータを指定した形式で出力するサンプルコードで説明しています →【Rubyによるデザインパターンまとめ11】ファクトリメソッドパターン - 銀行員からのRailsエンジニア

 

Buildパターン

オブジェクトを作るのに大量のコードを書かなければ行けない場合などに、オブジェクトの生成のためのコードを別のクラス(ビルダクラス)に分離するパターンです。

つまり、オブジェクトの生成手順(コード上ではコンストラクタ)を外出しします。複雑なオブジェクトの生成手順を分離することで、構造がシンプルになり、再利用性も高めることができます

ユーザーオブジェクトを作るサンプルコードで説明しています → 【Rubyによるデザインパターンまとめ12】ビルダパターン - 銀行員からのRailsエンジニア

 

Interpreterパターン

クラスで表現した文法規則で構文を解析し、その結果得られた手順に基づいて処理を実行するパターンです。

Interpreter」は「通訳」という意味です。

文字列をプラスマイナスできるサンプルコードで説明しています → 【Rubyによるデザインパターンまとめ13】インタプリタパターン - 銀行員からのRailsエンジニア

 

DSLドメイン特化言語

ある特定の問題を解決するために専用の言語を定義するパターンです。

Railsでは、Validations・ActiveRecord・Rake・RSpecなど幅広く使われています。

Railsのコードで実際に使われている箇所を題材に説明しています → 【Rubyによるデザインパターンまとめ14】DSL(ドメイン特化言語) - 銀行員からのRailsエンジニア

 

メタプログラミング

必要なコードを全て書いておくのではなく、実行時にプログラムに基づいて作り出します。

「attr_reader」と同じ機能をメタプログラミングで実装しながら説明しています → 【Rubyによるデザインパターンまとめ15】メタプログラミング - 銀行員からのRailsエンジニア

 

Convention over Configuration

日本語訳すると「設定より規約」で、規約に従うことで不必要な設定のコードを書く必要がなくなります

頭文字を取って「CoC」と略されます。

例えば、Rails の ActiveRecord では、users というテーブルは、 models ディレクトリにある user.rb というファイルにある User クラスで処理されます。さらに、users テーブルの name というカラムは、user オブジェクトの name フィールドに自動的に割り当てられます。
このように、規約に従うことでコードの記述量を減らすことでき、また誰が見ても分かりやすいコードになります。

Railsのscaffoldに似た機能を実装しながら説明しています → 【Rubyによるデザインパターンまとめ16】Convention over Configuration - 銀行員からのRailsエンジニア

 

デザインパターンをまとめてきた感想 

選択肢をいくつか持っていることによって状況に一番合った実装を選べる確率が上がるのかなあ、と思いました。自分の実装の幅を広げてくれるのでデザインパターンを知っているといいですね。学んで正解でした。

なんてことを、「あるコードを何種類かの方法でリファクタリングする」という内容の記事を書きながら思ったので、良ければ合わせて読んでみてください。

ストラテジーパターンを使ったリファクタリング例【Ruby】 - 銀行員からのRailsエンジニア

 

学んで終わりだと全く意味はないので、これから実務でのプロダクションコードにガンガンデザインパターンを入れていこうと思いますー!(先日も初めて1つ入れました!)

 

1冊で多くのことを学べたこの本は本当に良書だと思うので、Rubyをやっていてデザインパターンを学んでみたい方はぜひ読んでみてくださいー!(現在は新品を売っていなくて中古が高騰しちゃってるのがちょっとつらいですが..)

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

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

 

【Rubyによるデザインパターンまとめ16】Convention over Configuration

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

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

今回は Convention over Configuration についてまとめました。

f:id:ysk_pro:20200320150238p:plain

Convention over Configuration とは

日本語訳すると「設定より規約」で、規約に従うことで不必要な設定のコードを書く必要がなくなります

頭文字を取って「CoC」と略されます。

例えば、RailsActiveRecord では、users というテーブルは、 models ディレクトリにある user.rb というファイルにある User クラスで処理されます。さらに、users テーブルの name というカラムは、user オブジェクトの name フィールドに自動的に割り当てられます。
このように、規約に従うことでコードの記述量を減らすことでき、また誰が見ても分かりやすいコードになります。

コード

Rails はモデル・コントローラなどを自動で作成してくれる scaffold という機能があり、これも規約に従うことでコードの記述量を減らしてくれる CoC の一つです。

scaffold に似た、簡単な機能を作ってみましょう。

original_scaffold.rb ファイルを作成します。

name = ARGV[0]
class_name = name.capitalize + 'Controller'
file_name = name + '_controller.rb'

content = <<-"EOS"
class #{class_name}
  def hello
    puts 'Hello!'
  end
end
EOS

File.open(file_name, 'w') do |f|
  f.write(content)
end

このように「coc」を引数に渡して実行します。

ruby original_scaffold.rb coc

すると、「coc_controller.rb」が作成されました!

class CocController
  def hello
    puts 'Hello!'
  end
end

おわりに

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

Rubyによるデザインパターン の中では、様々の例を使って説明されていて分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

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

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

これで、16回に渡ったデザインパターンまとめブログが終わりました。

ここから、今までのデザインパターンまとめ記事の一覧を見ることができ流ので是非他のパターンも見てみてください。
デザインパターン カテゴリーの記事一覧 - 銀行員からのRailsエンジニアysk-pro.hatenablog.com

次は何をやっていこうかな〜〜

ストラテジーパターンを使ったリファクタリング例【Ruby】

例えば、何の変哲もないこんなクラスがあったとします。

class Sample
  def method_a
    # ・・・
    common
  end

  private

  def common
    # ・・・
    process_a
    # ・・・
  end

  def process_a
    # ・・・
    puts 'processAだよ'
  end
end

「# ・・・」となっているところには、様々な処理があるとします。

こんな感じで実行すると

Smaple.new.method_a

当然こんな結果があります。

processAだよ


この何の変哲もないクラスに、機能追加で publicな method_b を追加することになりました。

method_b内では、commonメソッド とほぼ同じですが、process_a メソッドを呼び出す部分だけ process_b メソッド を呼び出す処理に変えたいとします。

どう呼び出しを切り替えるかは置いておいて、とりあえず必要となる method_b メソッドと process_b メソッドを追加してみました。

class Sample
  def method_a
    # ・・・
    common
  end

  # 追加するメソッド
  def method_b
    # ・・・
    # ここにcommonメソッドっぽいやつを入れたい
  end

  private

  def common
    # ・・・
    process_a
    # ・・・
  end

  def process_a
    # ・・・
    puts 'processAだよ'
  end

  # 追加したメソッド
  def process_b
    # ・・・
    puts 'processBだよ'
  end
end

ここからが問題です。

みなさんだったらどのように実装するでしょうか。


(シンキングタイム)


いくつかの実装方法が考えられますね。

それでは1つ目から見てみましょう。

パターン①

class Sample
  def method_a
    # ・・・
    common_a
  end

  def method_b
    # ・・・
    common_b
  end

  private

  def common_a
    # ・・・
    process_a
    # ・・・
  end

  def common_b
    # ・・・
    process_b
    # ・・・
  end

  def process_a
    # ・・・
    puts 'processAだよ'
  end

  def process_b
    # ・・・
    puts 'processBだよ'
  end
end

common メソッドを、method_a で使うものを common_a、method_b で使うものを common_b と分けてみました。(もはや common では無いですね 笑)

このように実行すると

Sample.new.method_a
Sample.new.method_b

このような結果となり、正しく動作します。

processAだよ
processBだよ

ただ、process_a / process_b メソッド の呼び出し以外の箇所は全く同じなので、同じ処理が2回ずつ書かれてしまいました

これでは修正漏れなどが起こってしまいそうですね。

違うやり方を考えてみましょう。


パターン②

class Sample
  def method_a
    # ・・・
    common(:a)
  end

  def method_b
    # ・・・
    common(:b)
  end

  private

  def common(a_or_b)
    # ・・・
    send("process_#{a_or_b}")
    # ・・・
  end

  def process_a
    # ・・・
    puts 'processAだよ'
  end

  def process_b
    # ・・・
    puts 'processBだよ'
  end
end

先ほどと同じように実行すると、正しく動作しました。

common メソッドが引数を取るようにし、引数に process_a または process_b メソッドのどちらを呼ぶかの情報(:a / :b)を渡して、それを使って動的に process_a / process_b メソッドを呼び出しています

commonメソッドの共通部分は1つのままなので、パターン①より良さそうです。

これはかなり良さそうですが、もう一つやり方を考えてみます。


パターン③

class Sample
  def method_a
    # ・・・
    common(SampleA.new)
  end

  def method_b
    # ・・・
    common(SampleB.new)
  end

  private

  def common(a_or_b_instance)
    # ・・・
    a_or_b_instance.process
    # ・・・
  end
end

class SampleA
  def process
    # ・・・
    puts 'processAだよ'
  end
end

class SampleB
  def process
    # ・・・
    puts 'processBだよ'
  end
end

同じように実行すると、問題なく動作します。

パターン①、②と比べて思い切って構造を変えてみました。

method_a、method_b メソッドの変化する部分である processメソッド を SampleA、SampleB クラスに移動させ、common メソッドへの引数として SampleA、SampleB クラスのインスタンスを渡して、渡されたインスタンスの process メソッドを呼び出すことで処理を分けています。

クラスを分離することで、コードの見通しも良くなったと思います。

パターン③はデザインパターンの1つである、「ストラテジーパターン」を使っています。


パターン①〜③のどれが一番優れているかではなく、選択肢をいくつか持っていることによって状況に一番合った実装を選べるのかなあ、と思います。

自分の実装の幅を広げてくれるのでデザインパターンを知っているといいですね。


今回使ったストラテジーパターンについてはこちらの記事で紹介しているので、ご興味ある方は是非ご覧ください。
ysk-pro.hatenablog.com

【Rubyによるデザインパターンまとめ15】メタプログラミング

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

前回の記事(DSLドメイン特化言語)のまとめ)はこちらです。
【Rubyによるデザインパターンまとめ14】DSL(ドメイン特化言語) - 銀行員からのRailsエンジニア

今回は メタプログラミング についてまとめました。

attr_reader と同じ機能を、メタプログラミングを使って実装しながら説明しています。

f:id:ysk_pro:20200314140337p:plain

メタプログラミングとは

必要なコードを全て書いておくのではなく、実行時にプログラムに基づいて作り出します。

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

コード

Rubyのコードではお馴染みの「attr_reader」と同じ機能をメタプログラミングで実装してみましょう。

説明は不要だと思いますが、attr_reader はゲッターメソッドとも呼ばれ、クラスに

attr_reader :name

と記載するだけで、

def name
  @name
end

このように、インスタンス変数を取得するメソッドを定義してくれる便利な機能です。

attr_reader という同じ名前だと分かりづらいので、同じ機能を持った「metapro_reader」をメタプログラミングを使って実装します。

class Object
  def self.metapro_reader(name)
    code = "def #{name}
              @#{name}
            end"
    class_eval(code)
  end
end

class User
  metapro_reader :name

  def initialize(name)
    @name = name
  end
end

なんとこれだけです。

次のように実行すると

user = User.new('メタプロくん')
puts user.name

次の結果になります。

メタプロくん

「metapro_reader :name」のところをコメントアウトすると、このコードは当然エラーします。


コードについて説明していきます。

Objectクラスは全てのクラスの最終的な親クラスで、Objectクラスにメソッドを定義するとどこからでも呼び出すことができます。

本家の attr_reader もそうなのですが、metapro_reader はただのクラスメソッドです。

class_eval メソッドがキモで、引数として渡された文字列をクラス内でRubyプログラムとして実行してくれます。これにより、動的にメソッド(今回でいう metapro_reader)を定義することができます。


※ 本家の attr_reader メソッドは ObjectクラスにincludeされているModuleモジュールに書かれているのですが、効率化のためにRubyではなく C で書かれています。

おわりに

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

メタプログラミングと聞くとちょっと怖い気がしますが、Rubyのコード内でも使われている身近なものだと分かると思います。

Rubyによるデザインパターン の中では、attr_reader 以外にも多くの例を用いて説明されていて分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

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

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

また、以前読んだこちらもメタプログラミングについて多く説明がされておりとても面白かったです。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

次回は、Convention over Configurationについてまとめます。(次回でついに最終回です!)

来週も頑張ります!

(追記)
Convention over Configuration についてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com

【Rubyによるデザインパターンまとめ14】DSL(ドメイン特化言語)

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

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

今回は DSLドメイン特化言語 についてまとめました。

Railsのコードで実際に使われている箇所をピックアップして説明しています。
f:id:ysk_pro:20200307105417p:plain

DSLドメイン特化言語)とは

ある特定の問題を解決するために専用の言語を定義するパターンです。

Railsでは、Validations・ActiveRecord・Rake・RSpecなど幅広く使われています。

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

コード

Railsのコードのmodelでよく目にする、以下のようなバリデーションを実装したコードはDSLを活用しています。

class Item < ActiveRecord::Base
  validates :title, length: { maximum: 40 }
end

上記のvalidatesは、Railsのコードの中の以下メソッドを呼び出しています。

def validates(*attributes)
  defaults = attributes.extract_options!.dup
  validations = defaults.slice!(*_validates_default_keys)

  raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
  raise ArgumentError, "You need to supply at least one validation" if validations.empty?

  defaults[:attributes] = attributes

  validations.each do |key, options|
    key = "#{key.to_s.camelize}Validator"

    begin
      validator = key.include?("::") ? key.constantize : const_get(key)
    rescue NameError
      raise ArgumentError, "Unknown validator: '#{key}'"
    end

    next unless options

    validates_with(validator, defaults.merge(_parse_validates_options(options)))
  end
end

rails/validates.rb at 526dd6472b3f3ab72ecc2538067b6c94f395fa36 · rails/rails · GitHub

バリデーションのために専用の言語/記法(例: validates :title, length: { maximum: 40 } )を作ることによって可読性を上げています。

おわりに

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

Rubyによるデザインパターン の中では、ファイルバックアップ機能を作成するオリジナルのコードを使って説明されていて分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

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

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

次回は、メタプログラミングをまとめます。

来週も頑張ります!

(追記)
メタプログラミングについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com

【Rubyによるデザインパターンまとめ13】インタプリタパターン

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

前回の記事(ビルダパターンのまとめ)はこちらです。
【Rubyによるデザインパターンまとめ11】ファクトリメソッドパターン - 銀行員からのRailsエンジニア【Rubyによるデザインパターンまとめ12】ビルダパターン - 銀行員からのRailsエンジニア

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

今回は インタプリタInterpreter)パターン についてまとめました。

f:id:ysk_pro:20200229160054p:plain

インタプリタパターンとは

クラスで表現した文法規則で構文を解析し、その結果得られた手順に基づいて処理を実行するパターンです。

Interpreter」は「通訳」という意味です。

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

サンプルコード

文字列をプラスマイナスできるプログラムを考えます。

class Word
  def initialize(value)
    @value = value
  end

  def execute
    @value
  end
end

class Plus
  def initialize(first, second)
    @first = first
    @second = second
  end

  def execute
    @first.execute + @second.execute
  end
end

class Minus
  def initialize(first, second)
    @first = first
    @second = second
  end

  def execute
    index = @first.execute =~ /#{@second.execute}/
    second_index = index + @second.execute.length
    @first.execute[0,index] + @first.execute[second_index..-1]
  end
end

class Interpreter
  def self.parse(input)
    @waiting_second_word = false
    words = []
    operations = []
    input.split.each do |value|
      if value =~ /^[^+-].*/ && !@waiting_second_word
        words << Word.new(value)
      else
        if symbol = operations.pop()
          first = words.pop
          second = Word.new(value)
          case symbol
          when /\A\+/
            words << Word.new(Plus.new(first, second).execute)
          when /\A\-/
            words << Word.new(Minus.new(first, second).execute)
          end
          @waiting_second_word = false
        else
          @waiting_second_word = true
          operations << value
        end
      end
    end
    words.pop.execute
  end
end

次のように実行すると

puts Interpreter.parse("おはよう + ございます - ござい")

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

おはようます

サンプルコードはこちらを参考にさせていただきました。
GitHub - piscolomo/ruby-patterns: Examples of Patterns in Ruby

おわりに

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

Rubyによるデザインパターン の中でも、ファイル検索のプログラムを作る過程を通じて説明されており分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

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

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

次回は、DSLドメイン特化言語)をまとめます。

来週も頑張ります!

(追記)
DSLドメイン特化言語)についてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com

【Rubyによるデザインパターンまとめ12】ビルダパターン

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

前回の記事(ファクトリメソッドパターンのまとめ)はこちらです。
【Rubyによるデザインパターンまとめ11】ファクトリメソッドパターン - 銀行員からのRailsエンジニア

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

今回は ビルダ(Build)パターン についてまとめました。

f:id:ysk_pro:20200222105019p:plain

ビルダパターンとは

オブジェクトを作るのに大量のコードを書かなければ行けない場合などに、オブジェクトの生成のためのコードを別のクラス(ビルダクラス)に分離するパターンです。

つまり、オブジェクトの生成手順(コード上ではコンストラクタ)を外出しします。複雑なオブジェクトの生成手順を分離することで、構造がシンプルになり、再利用性も高めることができます

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

サンプルコード

ユーザーのオブジェクトを作るプログラムを考えてみましょう。

まずはビルダパターンを使わずに実装してみます。

class User
  attr_accessor :first_name, :last_name, :birthday, :gender, :status, :email, :password

  def initialize(first_name, last_name, birthday, gender, status, email, password)
    @first_name = first_name
    @last_name = last_name
    @birthday = birthday
    @gender = gender
    @status = status
    @email = email
    @password = password
  end
end

このように実行すると、

user = User.new('テスト', '太郎', '1991-08-11', 1, 1, 'test@example.com', 'pass1234')
p user

次のようにオブジェクトができていることが分かります。

#<UserBuilder:0x00007f911d911f08 @user=#<User:0x00007f911d911ee0 @first_name="テスト", @last_name="太郎", @gender=1, @status=1, @email="test@example.com", @password="pass1234">>

このコードはオブジェクトの生成時に大量のパラメータを渡す必要があり、今後新しいパラメータが追加された場合などには、どんどん複雑になり分かりにくくなってしまいます

これをビルダパターンを使って書き換えてみましょう。

class User
  attr_accessor :first_name, :last_name, :birthday, :gender, :status, :email, :password
end

class UserBuilder
  attr_reader :user

  def initialize
    @user = User.new
  end

  def set_name(first_name, last_name)
    @user.first_name = first_name
    @user.last_name = last_name
  end

  def set_birthday(birthday)
    @user.birthday = birthday
  end

  def set_as_man
    @user.gender = 1
  end

  def set_as_woman
    @user.gender = 2
  end

  def set_as_active
    @user.status = 1
  end

  def set_login_credectiols(email, password)
    @user.email = email
    @user.password = password
  end
end

次のように実行すると先ほどと同じ結果となり、ユーザーのオブジェクトを作ることができました。

user_builder = UserBuilder.new
user_builder.set_name('テスト', '太郎')
user_builder.set_as_man
user_builder.set_as_active
user_builder.set_login_credectiols('test@example.com', 'pass1234')
user = user_builder
p user

こうすることでオブジェクトの生成手順が分かりやすくなり、今後ユーザーと近いオブジェクトを作る際にはビルダクラスを共通化することもできるので再利用性も高まりました

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

おわりに

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

Rubyによるデザインパターン の中でも、ビルダクラスを使ってリファクタリングしていく形式で分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

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

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

次回は、インタプリタInterpreter)パターンをまとめます。

来週も頑張ります!

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