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

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

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

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

継承から委譲へ【リファクタリング Rubyエディションまとめ3】

こんにちは。

最近 リファクタリング Rubyエディション を読みました。本書は、復刻版が最近発売された名著でリファクタリングの様々なパターンRubyのサンプルコードを使って解説しています。

紹介されているリファクタリングのパターン数は膨大で全てをまとめるのは難しいので、いいなと思ったパターンを1つずつブログにまとめています。

今回はその第3弾で「継承から委譲へ」についてまとめます。
オリジナルの Ruby のサンプルコードを使いながら説明しています。

第2弾の「モジュールの抽出」のまとめはこちらです。
モジュールの抽出【リファクタリング Rubyエディションまとめ2】 - 銀行員からのRailsエンジニア

f:id:ysk_pro:20200505075342p:plain

「継承から委譲へ」とは

概要

継承を使っていた箇所を、継承の関係を解消して委譲を使うようにするリファクタリングです。

どんな時に使えるか

継承関係にあるものの、サブクラスがスーパークラスのインターフェースの一部しか使っていなかったり、スーパークラスのメソッドの多くがサブクラスにうまくかみ合っていない時に有効となります。

どのようにリファクタリングするか

サブクラスにスーパークラスのためのフィールドを作り、スーパークラスに処理を委譲するようにして、継承構造を解消します。

メリット

不要なメソッドを継承することがなくなり、予期せぬ副作用を防ぎつつコードの共通化を維持することができます。

サンプルコード

リファクタリング

サンプルコードを見ていきましょう。
こちらはリファクタリング前のコードです。

class Tasks < Array
  def finish(task)
    puts 'Well done!'
    delete(task)
  end

  def list
    each { |task| puts task }
  end
end

Array クラスを継承した Tasks クラスを作ってみました。

tasks = Tasks.new
tasks.push('clean room')
tasks.push('study ruby')

tasks.list
tasks.finish('clean room')
tasks.list

上記のように実行すると、下記の結果になります。

clean room
study ruby
Well done!
study ruby

Array クラスを継承してしていますが、Array クラスの一部のメソッド(push, delete, each)しか使っていませんね。

Tasks クラスに必要なメソッドのみを使えるよう、委譲を使ってリファクタリングしてみましょう。

リファクタリング

「継承から委譲へ」を使ってリファクタリングすると次のようになります。

require 'forwardable'

class Tasks
  extend Forwardable

  def_delegators :@tasks, :each, :delete, :push

  def initialize
    @tasks = [] # Array.new でも同じ
  end

  def finish(task)
    puts 'Well done!'
    delete(task)
  end

  def list
    each { |task| puts task }
  end
end

実行方法、結果はリファクタリング前と同じになります。

Tasks クラスは Array クラスを継承しなくてよくなり、Array クラスで初期化したインスタンス変数である @tasks に処理を委譲することで、Array クラスのメソッドを利用できています。

これで不要なメソッドの継承は防ぎつつ、利用したい Array クラスのメソッドを使えるようになりました

委譲の部分は、Forwardable モジュールの def_delegators メソッドを使うことで実現しています。

おわりに

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

継承関係がいいのか、委譲を使った方がいいのかは難しく、コードの増加とともに変更したくなるケースもあると思うので、この方法は覚えておくと役立ちそうですね。

リファクタリング Rubyエディションでは、Hashクラスを継承しているサンプルコードで丁寧に解説されていてとても分かりやすかったので、ご興味ある方は是非ご覧ください。

次回はまた違うパターンをまとめていきます!

(追記)
第4弾として「nullオブジェクトの導入」についてまとめました。
是非合わせてご覧ください。
ysk-pro.hatenablog.com

モジュールの抽出【リファクタリング Rubyエディションまとめ2】

こんにちは!

最近 リファクタリング Rubyエディション を読み始めました。本書は、復刻版が最近発売された名著でリファクタリングの様々なパターンをRubyのサンプルコードと共に紹介しています。

紹介されているリファクタリングのパターンの数は膨大なので、いいなと思ったパターンを1つずつブログにまとめています。

今回はその第2弾で「モジュールの抽出」についてまとめます。

第1弾の「委譲の隠蔽」のまとめはこちらです。
委譲の隠蔽【リファクタリング Rubyエディションまとめ1】 - 銀行員からのRailsエンジニア

f:id:ysk_pro:20200502084917p:plain

リファクタリング前のコード

早速サンプルコードを見ていきましょう。
こちらはリファクタリング前のコードです。

ItemクラスとUserクラスで、インスタンスの保存の前にデフォルトの名前を設定しています。

class Item < ApplicationRecord
  #・・・
  before_save :set_default_name

  def set_default_name
    self.name = default_name
  end
  #・・・
end

class User < ApplicationRecord
  #・・・
  before_save :set_default_name

  def set_default_name
    self.name = default_name
  end
  #・・・
end

このコードの問題点は、言うまでもなくコードが重複している点です。

ただ、set_default_name というメソッドは、Itemクラス・Userクラスそれぞれのインスタンスを更新しているため、別のクラスに共通のメソッドを作ってそのクラスのインスタンスに処理を委譲するというやり方ではうまくいきません。

今回のような、通化したいコードがそれぞれのクラスでしか意味を持たないケースで「モジュールの抽出」は有効です。

モジュールの抽出を使ったリファクタリング

モジュールの抽出を使ったリファクタリング後のコードはこのようになります。
Itemクラス、Userクラスでは新しく作った SetDefaultNameモジュールをincludeしているだけでとてもシンプルですよね。

module SetDefaultName
  def self.included(klass) # ①
    klass.class_eval do # ②
      before_save :set_default_name
    end
  end

  def set_default_name
    self.name = default_name
  end
end

class Item < ApplicationRecord
  #・・・
  include SetDefaultName
  #・・・
end

class User < ApplicationRecord
  #・・・
  include SetDefaultName
  #・・・
end

includedメソッド は、モジュール(今回だとSetDefaultName)が include された時に、include をしたクラス(今回だと Item, User)を引数に実行されるメソッドです。

class_evalメソッド は、ブロックに記載したメソッドをレシーバ(このコードだと klass)内に定義してくれるメソッドです。

つまり self.included メソッドでは、SetDefaultName モジュールが include されるタイミングで、include したクラスに before_save :set_default_name を定義しているのです。

「モジュールの抽出」という名前は、単に共通の処理をモジュールにして抽出していることを表しており、シンプルで覚えやすいですね。

おわりに

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

includedメソッドを使ってモジュールが include されたタイミングでメソッドを定義する方法は、知っておかないとこの方法を使っているコードを読むのが難しいので、覚えておいて損はないと思います。

このブログのサンプルコードは、全く同じではないもののリファクタリング Rubyエディションのサンプルコードを参考にさせていただきました。本書の中では、さらに丁寧に解説されていてとても分かりやすいので、ご興味ある方は是非ご覧ください。

次回はまた違うパターンをまとめていきます!

(追記)
第3弾として「継承から委譲へ」についてまとめました。
是非合わせてご覧ください。
ysk-pro.hatenablog.com

委譲の隠蔽【リファクタリング Rubyエディションまとめ1】

こんにちは。ゆうすけです。

先日ついに Rubyによるデザインパターン を読み終えた(!)ので、次は リファクタリング Rubyエディション を読み始めました。

リファクタリング Rubyエディションは、復刻版が最近発売された名著でリファクタリングの様々なパターンをRubyのサンプルコードと共に紹介していく形式です。

パターンの数は膨大なので、いいなと思ったパターンを1つずつブログにまとめていこうと思います。

今回はその第一弾で「委譲の隠蔽」についてまとめます。

問題のあるコード

例えば、こんなコードがあったとします。

class Article
  attr_accessor :blog
end

class Blog
  attr_reader :writer

  def initialize(writer)
    @writer = writer
  end
end

前提として、ブログ(Blog)の中にたくさんの記事(Article)があり、同じブログに属している記事の作者は同じとします。

ある記事の作者(writer)が知りたい場合は、下記の実行コードの「article1.blog.writer」のように呼び出す必要があります。

article1 = Article.new
blog1 = Blog.new('ブロガーくん')
article1.blog = blog1

puts article1.blog.writer # -> ブロガーくん

このコードだと、呼び出す側が「作者の管理はブログが行なっている」という知識を持っている必要があります。(言い換えると、article1.blog.writer という呼び出し方を知っている必要があるということ)

知識が必要だということは、呼び出す側からブログクラスへの依存が生じており、ブログクラスに変更があった際には呼び出す側にも影響が生じてしまいます。辛いですね。。

委譲の隠蔽を使ったリファクタリング

これを、記事クラスに簡単な委譲メソッド(writer メソッド)を作って改善します。

class Article
  attr_accessor :blog

  def writer
    blog.writer
  end
end

class Blog
  attr_reader :writer

  def initialize(writer)
    @writer = writer
  end
end

こうすることによって、下記実行コードのように「article1.writer」と呼び出せるようになり(ブログクラスへの処理の委譲を記事クラスが隠蔽している!)、「作者の管理はブログが行なっている」という知識を呼び出す側が持つ必要がなくなります。(言い換えると、article1.writer と呼び出せばいいことだけを知っていればよいということ)

先ほどの問題点をクリアしていますね!

article1 = Article.new
blog1 = Blog.new('ブロガーくん')
article1.blog = blog1

puts article1.writer # -> ブロガーくん

図で表すとこのようなイメージです。
矢印の向いた先に依存していることを示しています。

依存関係を表した図

図から分かるように、呼び出し側からブログクラスへの依存がなくなり、ブログクラスの変更の影響を呼び出し側が受けなくなりました。これは嬉しいですね。

「委譲の隠蔽」とは、元々は記事クラスが作者を取得するための処理をブログクラスに委譲していたところを、記事クラスの writer メソッドに隠蔽した(呼び出す側から分からなくした)という意味です。

Forwardableモジュールを使うと

やっていることは同じなのですが、Ruby標準ライブラリに含まれている Forwardable モジュールを使うことで以下のように書くこともできます。

require 'forwardable'

class Article
  extend Forwardable

  def_delegator :blog, :writer

  attr_accessor :blog
end

class Blog
  attr_reader :writer

  def initialize(writer)
    @writer = writer
  end
end

def_delegator は、2つ目の引数のメソッド(この場合は writer)を1つ目の引数のオブジェクト(この場合は blog)へ委譲します。

よりスッキリしていい感じですね。

おわりに

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

サンプルコードと依存関係の図は リファクタリング Rubyエディションを参考にさせていただきました。本書の中では、Person クラスと Department(部門)クラスの関係を例に説明しています。

「委譲の隠蔽」使えそうですね。

次回はまた違うパターンをまとめていきます!

(追記)
第2弾として「モジュールの抽出」についてまとめました。
是非合わせてご覧ください。
ysk-pro.hatenablog.com

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回に渡ったデザインパターンまとめブログが終わりました!

実務でどんどん使っていこうと思います!

(追記)
デザインパターンをまとめた記事を書いたので、是非合わせてご覧ください。
ysk-pro.hatenablog.com

Rubyでのデザインパターンの適用例(ストラテジーパターン、テンプレートメソッドパターン)

デザインパターンって実際に適用するのなかなか難しいですよね..

僕も一通り学んできたのですが、なかなか実務のコードでうまく使えなくて悩んでます。

すごくシンプルな Rubyのコードで、デザインパターンが適用できるケースを考えてみたので宜しければ参考にしてみてください。

f:id:ysk_pro:20200503115503p:plain

元々のコード

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

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メソッドとほぼ同じですが下記の点だけが違ったメソッドを使いたいです。
<common メソッドと必要となったメソッドの異なる点>
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

ここで考えます。

どのように実装するとよいでしょうか。


ぜひ、自分だったらどのような実装をするか考えてみてください。


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

実装例① 〜愚直にいくぜ〜

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だよ

機能としてはうまくいきました!

ただ、common_a / common_b メソッドは、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 { process_a }
  end

  def method_b
    # ・・・
    common { process_b }
  end

  private

  def common
    # ・・・
    yield
    # ・・・
  end

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

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

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

commonメソッドにブロックを渡して、yield で実行することによって処理を分けてみました。

これもスッキリしていて良さそうですよね。

ここから、デザインパターンを適用したリファクタリングを考えてみます。

実装例④ 〜ストラテジーパターン〜

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つである、「ストラテジーパターン」を使っています。

実装例⑤ 〜テンプレートメソッドパターン〜

class Sample
  def method_a
    # ・・・
    common
  end

  def method_b
    # ・・・
    common
  end

  private

  def common
    # ・・・
    process
  end
end

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

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

次のように実行すると問題なく動作します。

SampleA.new.method_a
SampleB.new.method_b

Sampleクラスから、SampleA・SampleBクラスをして、継承したクラスのインスタンスを使うことで、process メソッドの処理を分けています。

継承を使って違和感のないクラス構成であればスッキリしていて良さそうですね。

パターン⑤はデザインパターンの1つである、「テンプレートメソッドパターン」を使っています。

おわりに

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

同じコードについて5通りに書き方をしてみました。

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

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

今回使ったデザインパターンについてはこちらの記事で紹介しているので、ご興味ある方は是非ご覧ください。

ストラテジーパターンについてはこちら
ysk-pro.hatenablog.com

テンプレートメソッドパターンについてはこちら
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