酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

アダプタ Ruby 2.0.0 デザインパターン速攻習得[Adapter][Design Pattern]

GoFのデザインパターン(Design Pattern)のアダプタ(Adapter) をRubyによるサンプルコードで紹介します。

アダプタは既存のインタフェースとの橋渡しをするためのデザインパターンです。

アダプタは現実世界の変換コネクタのようなものです。直接つながらないコネクタと差込口であっても、それらの間を変換コネクタが結びつけます。コネクタと差込口にカスタマイズが不要な点がアダプタの利点です。


Included file ‘custom/google_ads_yoko_naga.html’ not found in _includes directory

アダプタが利用される場面

Adapterパターンは次のような場面でよく使います。

関連性・互換性のないオブジェクト同士を結びつける必要がある
他のコンポーネントへの変更ができるようにする

アダプタの構成要素

アダプタの構成要素は次の4つです。

利用者(Client):ターゲットのメソッドを呼び出す
ターゲット(Target):インターフェースを既定する
アダプタ(Adapter):アダプティのインターフェースを変換してターゲット向けのインターフェースを提供
アダプティ(Adaptee):実際に動作する既存のクラス

サンプルソース

次のようなモデルを例にアダプタパターンを説明していきます。

Client: Printクラスを使う側

Printerクラス(Target):Clientが使っているメソッドを持つ
  print_weak: カッコに囲って文字列を表示する
  printer_strong: アスタリスクで囲って文字列を表示する

OldPrinterクラス(Adaptee):既に存在していたオブジェクト
  show_with_paren: 記述を弱めて書く
  show_with_aster: 記述を強めて書く

ClientはPrinterクラスのメソッドを使うことはできますが、Oldprinterクラスはメソッドの名前/定義が異なるため、改造無しに使うことができません。そこでAdapterデザインパターンを適用してClientがOldPrinterクラスを使えるようにします。

まずは、Printerクラスを確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 利用者(Client)へのインターフェイス (Target)
class Printer
  def initialize(obj)
    @obj = obj
  end

  def print_weak
    @obj.print_weak
  end

  def print_strong
    @obj.print_strong
  end
end

次にOldPrinterクラスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Targetにはないインターフェイスを持つ (Adaptee)
class OldPrinter
  def initialize(string)
    @string = string.dup
  end

  # カッコに囲って文字列を表示する
  def show_with_paren
    puts "(#{@string})"
  end

  # アスタリスクで囲って文字列を表示する
  def show_with_aster
    puts "*#{@string}*"
  end
end

このOldPrinterクラスのメソッドをClientが使えるPrinterクラスのインターフェースにするAdapterクラスを作成します。このクラスには、次の2つのメソッドを定義します。

* #print_weak OldPrinter#show_with_parenを呼び出す
* #print_strong OldPrinter#show_with_asterを呼び出す
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Targetが利用できるインターフェイスに変換 (Adapter)
class Adapter
  def initialize(string)
    @old_printer = OldPrinter.new(string)
  end

  def print_weak
    @old_printer.show_with_paren
  end

  def print_strong
    @old_printer.show_with_aster
  end
end

以上がソースコードです。
では結果を確認するために、ClientにTargetを動かしてもらいます。

1
2
3
4
5
6
7
# 利用者(Client)
p = Printer.new(Adapter.new("Hello"))
p.print_weak
#=> (Hello)

p.print_strong
#=> *Hello*

ClientはTargetのメソッドを呼び出しているだけですが、Adapterを介して接続したOldPrinterのメソッドにアクセスできています。このようにAdapterクラスによってPrinter/Oldprinterに変更が不要である点がAdapterデザインパターンの特徴です。

特異メソッドを使ったAdapter

ここでRubyの特異メソッドを使ってAdapterを表現した場合のコードを示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# Targetにはないインターフェイスを持つ (Adaptee)
class OldPrinter
  def initialize(string)
    @string = string.dup
  end

  def show_with_bracket
    puts "[#{@string}]"
  end

  def show_with_asterisk
    puts "**#{@string}**"
  end
end

# 利用者(Client)へのインターフェイス (Target)
class Printer
  def initialize(obj)
    @obj = obj
  end

  def print_weak
    @obj.print_weak
  end

  def print_strong
    @obj.print_strong
  end
end

# textオブジェクト(OldPrinter)にAdapterの役割を持つ得意メソッドを追加
text = OldPrinter.new("Hello")
def text.print_weak
  show_with_bracket
end
def text.print_strong
  show_with_asterisk
end

# 利用者(Client)
p = Printer.new(text)

p.print_weak
#=> [Hello]

p.print_strong
#=> **Hello**

このように先ほどよりもシンプルなコードでAdapterを表現できていることがわかります。

このサンプルソースはGitHubにも置いています。

サンプルソース(GitHub)

クラス変更とアダプタ適用のどちらを選択すべきか?

必要なインターフェースを変更するのは、クラスや特定のインスタンスだけを直接変更したほうがずっとシンプルなコードになります。ではどのような場面でアダプタを適用すべきでしょうか?

以下の判断材料を元に、「クラス変更」か「アダプタ適用」を選択して下さい。

* 問題のクラスの理解がある、変更が比較的少ない => クラス変更
* オブジェクトが複雑/完全な理解がない => アダプタ適用

Special Thanks

Adapter

デザインパターン-Adapter

第2回 Iteratorパターン/Adapterパターン

2.Adapter パターン 1

Adapter パターン - アダプターパターン

Ruby でデザインパターン2-2: Adapter(委譲)

Amazon.co.jp: Rubyによるデザインパターン: Russ Olsen, ラス・オルセン, 小林 健一, 菅野 裕, 吉野 雅人, 山岸 夢人, 小島 努: 本

変更来歴

12/10 09:00 GitHubへのサンプルソースの設置。導入文の修正
12/11 00:00 書籍へのリンクをAmazon アフィリエイトに変更
12/12 23:45 ソースコードに説明を追加
06/21 09:25 Ruby2.0.0対応、読みづらい部分を修正

おすすめの書籍