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

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

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

委譲の隠蔽【リファクタリング 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