Edited at

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

More than 3 years have passed since last update.

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クラスの構築のほうが有効です。