8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rubyデザインパターン 6日目 : Adapter

Last updated at Posted at 2015-07-31

Rubyデザインパターン学習のために、自分なりに読書の結果をまとめていくことに決めました。第6日目はAdapterです。(http://www.amazon.co.jp/gp/product/4894712857/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4894712857&linkCode=as2&tag=morizyun00-22)

スクリーンショット 2015-07-27 11.25.28.png

 6日目 Adapter

6日目はAdapterパターンです。

イメージとしては、名前の通り、変換器のような機能を作る時のパターンです。

具体的なコードに移ります。

Adapter サンプルコード

class Render 
  def render(text_object) 
    @text = text_object.text
    @size = text_object.size_inches
    @color = text_object.color
    printData
  end

  private
    def printData
      puts @text
      puts @size
      puts @color
    end
end

これは与えられたオブジェクトの情報を表示するクラスです。

次にデータを提供するオブジェクトに移ります。

class TextObject
  attr_reader :text, :size_inches, :color

  def initialize(text, size_inches, color)
    @text = text
    @size_inches = size_inches
    @color = color
  end
end

実際にデータを表示する時には、

text_object = TextObject.new("テキストです", 12, "red")

render_object = Render.new
render_object.render(text_object)

こういった形になると思います。何も問題ありませんね。

さて、次にこの状態から、他にもRenderクラスによって表示させたいオブジェクトができたとしましょう。

属性の名前がちょっと違う!?

さて、次に表示させたいオブジェクトがこんなクラスだった時はどうすればいいでしょうか?


class BritishTextObject
  attr_reader :string, :size_mm, :colour
  # ....
end

先ほどのTextObjectと比べてみましょう。

class TextObject
  attr_reader :text, :size_inches, :color
  #....
end

データ型は同じだとして、属性名が異なっています。
これでは、データを表示させるためのRenderクラスで不具合が起きてしまいます!

こういった時に**Adapter(変換器)**が解決法を導いてくれます。

class BritishTextObjectAdapter < TextObject
  def initialize(bto)
    @bto = bto
  end

  def text
    return @bto.string
  end

  def size_inches
    return @bto.size_mm / 25.4
  end

  def color
    return @bto.colour
  end
end

使い方は簡単



class Render 
  def render(text_object) 
    @text = text_object.text
    @size = text_object.size_inches
    @color = text_object.color
    printData
  end

  private
  def printData
    puts @text
    puts @size
    puts @color
  end
end

class TextObject
  attr_reader :text, :size_inches, :color

  def initialize(text, size_inches, color)
    @text = text
    @size_inches = size_inches
    @color = color
  end
end


class BritishTextObject
  attr_reader :string, :size_mm, :colour

  def initialize(string, size_mm, colour)
    @string = string
    @size_mm = size_mm
    @colour = colour
  end
end

class BritishTextObjectAdapter
  def initialize(bto)
    @bto = bto
  end

  def text
    @bto.string
  end

  def size_inches
    @bto.size_mm / 25.4
  end

  def color
    @bto.colour
  end
end


british_text_object = BritishTextObject.new("Momozono", 12, "red")

fixed_british_text_object = BritishTextObjectAdapter.new(british_text_object) #この時点で属性名が変換される

render_object = Render.new
render_object.render(fixed_british_text_object)

BritishTextObjectAdapterをクラスとして作成し、引数としてBritishTextObjectを迎える形でRenderクラスで出力する際に必要な属性名に変換しています。

Rubyらしい構成に...

上記の方法は結構堅苦しい方法です。ここからコードをRubyらしくリファクタリングしてみましょう。

ここではオープンクラスを利用します。

オープンクラスってなんやっけ?

class Array
  def shuffle
    "シャッフルでけへんで!!!"
  end
end

array = %w[hoge huga foobar]
array.shuffle # => シャッフルでけへんで!!!

オープンクラスを使うとこういった形で、元より定義されているメソッドを上書きしたり、新しいメソッドを追加したりすることができます。モンキーパッチとも言いますね。

AdapterをRubyのオープンクラスで表現してみましょう。

class BritishTextObject
  attr_reader :string, :size_mm, :colour

  def initialize(string, size_mm, colour)
    @string = string
    @size_mm = size_mm
    @colour = colour
  end
end

class BritishTextObject
  def text
    return string
  end

  def size_inches
    return size_mm / 25.4
  end

  def color
    return colour
  end
end

british_text_object = BritishTextObject.new("momozonno", 12, "red")

british_text_object.text # => "momozono"
british_text_object.size_inches# => 0.4724409448818898
british_text_object.color# => "red"

わざわざAdapterクラスを作ることなく、Adapterの機能が完全に再現できていますね。

けど、クラス内部あんまりいじくりたくない...

そんな時もあるでしょう。そういうときは特異メソッドで対応するのがいい解決法です。
Rubyにおける特異メソッドは、オブジェクト(ここではインスタンス)単体だけに特定のメソッドを持たせたいときに便利です。

大事な大事なクラスを汚染したりすることもありません!

bto = BritishTextObject.new('hello', 50.8, "rainbow")

class << bto
  def color
    colour
  end

  def text
    string
  end

  def size_inches
    size_mm / 25.4
  end
end

bto.color # => "rainbow"

 まとめ

Adapterは、似たようなオブジェクトの属性名の変更を可能にしました。
Adapterをクラスで定義したり、オープンクラスでメソッドを追加したり、特異メソッドでクラス汚染を避けたりと、いろいろな方法があります。
どれを選択するかは、結局適用するプロジェクトによるでしょう。
ただし、大まかに選択する基準はあるようです。

Ruby式オープンクラスが有効な場合

  • 変更が単純なとき
  • クラスの仕様、機能が細部までわかるとき

Adapterクラスの構築が有効な場合

  • そのクラスの仕様がわからない
  • カプセル化という点ではAdapterクラスの構築のほうが有効です。
8
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?